From d39ebcafd9c26ec8cd1dfc787b1a4997c914de7f Mon Sep 17 00:00:00 2001 From: Bill Talcott Date: Mon, 6 Mar 2023 12:11:16 -0500 Subject: [PATCH] edit, standardize hypermedia-driven with hyphen --- book/CH00_Introduction.adoc | 6 +- book/CH01_HypermediaAReintroduction.adoc | 2 +- book/CH04_BuildingASimpleWebApplication.adoc | 2 +- book/CH05_ExtendingHTMLAsHypermedia.adoc | 2 +- book/CH07_MorehtmxPatterns.adoc | 2 +- book/CH09_TricksOfThehtmxMasters.adoc | 8 +- ...H10_ScriptingInAHypermediaApplication.adoc | 28 +- book/CH11_JSONDataAPIs.adoc | 4 +- book/CH15_Conclusion.adoc | 2 +- book/HypermediaSystems_js_fonts2.html | 39231 +++++++++------- 10 files changed, 23107 insertions(+), 16180 deletions(-) diff --git a/book/CH00_Introduction.adoc b/book/CH00_Introduction.adoc index d3085ec..9ea5fdd 100644 --- a/book/CH00_Introduction.adoc +++ b/book/CH00_Introduction.adoc @@ -37,15 +37,15 @@ JavaScript library. In these applications HTML becomes a (somewhat awkward) graphical interface description language that is used because, for historical reasons, that's what happens to be there, in the browser. -Applications built in this style are not _hypermedia driven_: they do not take advantage of the underlying hypermedia +Applications built in this style are not _hypermedia-driven_: they do not take advantage of the underlying hypermedia system of the web. -To explain what a hypermedia driven application looks like, and to contrast it with the popular SPA approach of today, +To explain what a hypermedia-driven application looks like, and to contrast it with the popular SPA approach of today, we need to first explore the entire _hypermedia system_ of the web, beyond just discussing HTML. We need to look at the _network architecture_ of the original web, including how a web server delivers a hypermedia API, and how to effectively use the hypermedia features available in the hypermedia _client_ (e.g. the browser). -Each of these are important aspects of building an effective hypermedia driven application, and it is the entire +Each of these are important aspects of building an effective hypermedia-driven application, and it is the entire _hypermedia system_ that comes together to make hypermedia such a powerful architecture. == What is a Hypermedia System? diff --git a/book/CH01_HypermediaAReintroduction.adoc b/book/CH01_HypermediaAReintroduction.adoc index 9744850..8cc755b 100644 --- a/book/CH01_HypermediaAReintroduction.adoc +++ b/book/CH01_HypermediaAReintroduction.adoc @@ -527,7 +527,7 @@ without compromising your user interface. You might even be able to use the hype needs. Rather than having an SPA with a bit of hypermedia around the edges, or some mix of the two approaches, you can often create -a web application that is _primarily_ or _entirely_ hypermedia driven, and that still satisfies the interactivity that your +a web application that is _primarily_ or _entirely_ hypermedia-driven, and that still satisfies the interactivity that your users require. This can _tremendously_ simplify your web application and produce a much more coherent and understandable piece of diff --git a/book/CH04_BuildingASimpleWebApplication.adoc b/book/CH04_BuildingASimpleWebApplication.adoc index 856a724..fb03ba4 100644 --- a/book/CH04_BuildingASimpleWebApplication.adoc +++ b/book/CH04_BuildingASimpleWebApplication.adoc @@ -752,7 +752,7 @@ notion of "components". In JavaScript-oriented applications it is common to bre client-side components that are then composed together. These components are often developed and tested in isolation and provide a nice abstraction for developers to create testable code. -With Hypermedia Driven Applications, in contrast, you factor your application on the server side. As we said, the above form could be +With Hypermedia-Driven Applications, in contrast, you factor your application on the server side. As we said, the above form could be refactored into a shared template between the edit and create templates, allowing you to achieve a reusable and DRY (Don't Repeat Yourself) implementation. diff --git a/book/CH05_ExtendingHTMLAsHypermedia.adoc b/book/CH05_ExtendingHTMLAsHypermedia.adoc index 2a3826b..0e30f29 100644 --- a/book/CH05_ExtendingHTMLAsHypermedia.adoc +++ b/book/CH05_ExtendingHTMLAsHypermedia.adoc @@ -180,7 +180,7 @@ 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 diff --git a/book/CH07_MorehtmxPatterns.adoc b/book/CH07_MorehtmxPatterns.adoc index c37ef90..f283e76 100644 --- a/book/CH07_MorehtmxPatterns.adoc +++ b/book/CH07_MorehtmxPatterns.adoc @@ -827,7 +827,7 @@ image::screenshot_stacked_actions.png["All 3 table rows have Edit, View, Delete It would be nice if we didn't show the actions all in a row, and, additionally, it would be nice if we only showed the actions when the user indicated interest in a given row. We will return to this problem after we look at the relationship -between scripting and a Hypermedia Driven Application in a later chapter. +between scripting and a Hypermedia-Driven Application in a later chapter. For now, let's just tolerate this less-than-ideal user interface, knowing that we will fix it later. **** diff --git a/book/CH09_TricksOfThehtmxMasters.adoc b/book/CH09_TricksOfThehtmxMasters.adoc index 9833090..b9df657 100644 --- a/book/CH09_TricksOfThehtmxMasters.adoc +++ b/book/CH09_TricksOfThehtmxMasters.adoc @@ -43,7 +43,7 @@ To specify how to swap the returned HTML content into the DOM To specify where in the DOM to swap the returned HTML content Let's dive into 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. +options that might be useful when you are creating more advanced Hypermedia-Driven Applications. === hx-swap @@ -201,7 +201,7 @@ The `hx-trigger` attribute certainly is the most complex in htmx. More details a === Other Attributes -Htmx offers many other less commonly used attributes for fine-tuning the behavior of your Hypermedia Driven Application. +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: @@ -271,7 +271,7 @@ The `hx-sync` attribute supports a few different strategies that allow you to, f in flight, or queue requests with a particular queuing strategy. You can find complete documentation, as well as examples, at the https://htmx.org/attributes/hx-sync/[documentation page] for `hx-sync`. -As you can see, htmx offers a lot of attribute-driven functionality for more advanced Hypermedia Driven Applications. +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 https://htmx.org/reference/#attributes[on the htmx website]. == Events @@ -497,7 +497,7 @@ codes in your application. Full documentation on the `htmx:beforeSwap` event ca 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 +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. diff --git a/book/CH10_ScriptingInAHypermediaApplication.adoc b/book/CH10_ScriptingInAHypermediaApplication.adoc index d6ffdb1..2e5a203 100644 --- a/book/CH10_ScriptingInAHypermediaApplication.adoc +++ b/book/CH10_ScriptingInAHypermediaApplication.adoc @@ -22,7 +22,7 @@ and interactive experiences, including scripting. It is true in many ways that HTML, as specified and implemented, lacks affordances needed to build the kinds of applications that were implemented within those older systems. None of this means, however, that hypermedia's _purpose_ is "`documents`" -over "`applications`". +over "`applications.`" Rather, while the theoretical foundation is there, the implementation is underdeveloped. With JavaScript being the only extension point and hypermedia controls not being well integrated to JavaScript (why can't one click a link without @@ -33,19 +33,19 @@ A goal of this book is to show that it is possible to build sophisticated web ap of the web, hypermedia, without the application developer needing to reach for the abstractions provided by the large, popular JavaScript frameworks. -htmx itself is, of course, written in JavaScript, and one of its advantages is that hypermedia interactions that go +Htmx itself is, of course, written in JavaScript, and one of its advantages is that hypermedia interactions that go through htmx expose a rich interface to JavaScript code with configuration, events, and htmx's own extension support. Because htmx expands the expressiveness of HTML so much it removes the need for scripting in many situations. This makes htmx attractive to people who don't want to write JavaScript, and there are many of those sorts of developers, -sick to death of the complexity of Single Page Application frameworks. +wary of the complexity of Single Page Application frameworks. However, dunking on JavaScript is not the aim of the htmx project. The goal of htmx is not less JavaScript, per se, but rather less code. Scripting has been a massive force multiplier for the web. Using scripting, web application developers are not only able -to enhance their HTML websites, but also create fully-fledged client-side applications that can often compete with +to enhance their HTML websites, but also create full-fledged client-side applications that can often compete with native, thick client applications. This JavaScript-centric approach to building web applications is a testament to the power of the web and to the sophistication @@ -77,7 +77,7 @@ other concerns of our application. Note also that, especially in web development parlance, the humble "`server`" is usually a whole fleet of racks, virtual machines, containers and more. Even a worldwide network of datacenters is reduced to "`the server`" when discussing -the server-side of a Hypermedia Driven-Application. +the server-side of a Hypermedia-Driven Application. **** Satisfying these two constraints sometimes requires us to diverge from what is typically considered best practice for @@ -168,7 +168,7 @@ You can just start writing JavaScript in your web application, and it will simpl That's the good news. The bad news is that, despite improvements over the last decade, JavaScript has some significant limitations as a scripting language that can make it a less than ideal as a stand-alone scripting technology for -Hypermedia Driven Applications: +Hypermedia-Driven Applications: * Being as established as it is, it has accreted a lot of features and warts. * It has a complicated and confusing set of features for working with asynchronous code. @@ -353,7 +353,7 @@ All of these tools achieve Locality of Behaviour by having you embed attributes having code look up elements in a document through CSS selectors in order to add event listeners onto them. **** -In a Hypermedia Driven Application, we feel that the Locality of Behaviour design principle is often more important than +In a Hypermedia-Driven Application, we feel that the Locality of Behaviour design principle is often more important than the more traditional Separation of Concerns design principle. ==== What to do with our counter? @@ -442,7 +442,7 @@ split out to a separate file: * Reuse is _easy_ -- you can create another counter component on the page and it will just work. * The code is _well-organized_ -- one behavior per file. -All in all, RSJS is a good way to structure your vanilla JavaScript in a Hypermedia Driven Application. So long as the +All in all, RSJS is a good way to structure your vanilla JavaScript in a Hypermedia-Driven Application. So long as the JavaScript isn't communicating with a server via a plain data JSON API, or holding a bunch of internal state outside of the DOM, this is perfectly compatible with the HDA approach. @@ -941,7 +941,7 @@ the element that has the `x-data` attribute declared on it. In this case, it wi contacts. The `target`, or where the response HTML will be placed, is just the entire document's body, since the `DELETE` handler returns a whole page when it completes. -Note that we are using Alpine here in a Hypermedia Driven Application compatible manner. We _could_ have issued an +Note that we are using Alpine here in a Hypermedia-Driven Application compatible manner. We _could_ have issued an AJAX request directly from Alpine and perhaps updated an `x-data` property depending on the results of that request. But, instead, we delegated to htmx's JavaScript API, which made a _hypermedia exchange_ with the server. @@ -1136,14 +1136,14 @@ Additionally, since +_hyperscript+ embeds so well in HTML, it keeps the focus _o scripting logic. Taken all together, given a certain style of scripting and certain scripting needs, +_hyperscript+ can provide an -excellent scripting experience for your Hypermedia Driven Application. It is a small and obscure programming +excellent scripting experience for your Hypermedia-Driven Application. It is a small and obscure programming language, so we won't blame you if you decide to pass on it, but it is worth a look to understand what it is trying to achieve. == Using Off-the-Shelf Components That concludes our look at three different options for _your_ scripting infrastructure, that is, the code that _you_ write -to enhance your Hypermedia Driven Application. However, there is another major area to consider when discussing client +to enhance your Hypermedia-Driven Application. However, there is another major area to consider when discussing client side scripting: "`off the shelf`" components. That is, JavaScript libraries that other people have created that offer some sort of functionality, such as showing modal dialogs. @@ -1151,12 +1151,12 @@ Components have become very popular in the web development works, with libraries providing rich user experiences with very little JavaScript code on the part of a user. Unfortunately, if these libraries aren't integrated well into a website, they can begin to make an application feel "`patched together`". Furthermore, some libraries go beyond simple DOM manipulation, and require that you integrate with a server endpoint, almost invariably -with a JSON data API. This means you are no longer building a Hypermedia Driven Application, simply because a particular +with a JSON data API. This means you are no longer building a Hypermedia-Driven Application, simply because a particular widget demands something different. A shame! === Integration Options -The best JavaScript libraries to work with when you are building a Hypermedia Driven Application are ones that: +The best JavaScript libraries to work with when you are building a Hypermedia-Driven Application are ones that: * Mutate the DOM but don't communicate with a server over JSON * Respect HTML norms (e.g. using `input` elements to store values) @@ -1289,7 +1289,7 @@ ____ In case of conflict, consider users over authors over implementors over specifiers over theoretical purity. ____ -We have shown you quite a few tools and techniques for scripting in a Hypermedia Driven Application. How should you +We have shown you quite a few tools and techniques for scripting in a Hypermedia-Driven Application. How should you pick between them? The sad truth is that there will never be a single, always correct answer to this question. Are you committed to vanilla JavaScript-only, perhaps due to company policy? Well, you can use vanilla JavaScript effectively diff --git a/book/CH11_JSONDataAPIs.adoc b/book/CH11_JSONDataAPIs.adoc index af73564..f4b858d 100644 --- a/book/CH11_JSONDataAPIs.adoc +++ b/book/CH11_JSONDataAPIs.adoc @@ -1,5 +1,5 @@ -= JSON Data APIs & Hypermedia Driven Applications += JSON Data APIs & Hypermedia-Driven Applications :chapter: 11 :url: ./json-data-apis/ @@ -468,7 +468,7 @@ Maybe, but maybe not. This was a pretty specific need for our web application, our JSON API, it doesn't make sense to include it for JSON consumers. And what if, by some miracle, the performance issues with `Contact.count()` that we were addressing with the Lazy Load -pattern goes away? Well, in our Hypermedia Driven Application we can simply revert to the old code and include the +pattern goes away? Well, in our Hypermedia-Driven Application we can simply revert to the old code and include the count directly in the request to `/contacts`. We can remove the `contacts/count` end point and all the logic associated with it. Because of the uniform interface of hypermedia, the system will continue to work just fine. diff --git a/book/CH15_Conclusion.adoc b/book/CH15_Conclusion.adoc index 28dcd93..bb27f3e 100644 --- a/book/CH15_Conclusion.adoc +++ b/book/CH15_Conclusion.adoc @@ -26,7 +26,7 @@ HTML: We were able to build user interfaces that many developers would assume require a significant amount of client-side JavaScript, but using only hypermedia concepts. -The Hypermedia Driven Application approach is not right for every application (after all, no approach is right for +The Hypermedia-Driven Application approach is not right for every application (after all, no approach is right for _every_ application) but, for many applications, the increased flexibility and simplicity of hypermedia can be a huge benefit. And, even if your application wouldn't benefit from this approach, it is at least worthwhile to _understand_ the approach, its strengths and weaknesses, and how it differs from the approach you are taking. The original web diff --git a/book/HypermediaSystems_js_fonts2.html b/book/HypermediaSystems_js_fonts2.html index f5bbbaf..7281766 100644 --- a/book/HypermediaSystems_js_fonts2.html +++ b/book/HypermediaSystems_js_fonts2.html @@ -1,744 +1,3763 @@ - - + + + - - - + html { + font-family: var(--main-font); + line-height: var(--rhythm); + background: var(--bg); + color: var(--fg); + scroll-padding-block-start: calc(4*var(--gap)) + } + + footer, + header, + section+section { + margin-block: calc(2*var(--gap)) + } + + aside.big, + nav a { + color: var(--accent) + } + + nav a { + text-decoration: none + } + + aside h1, + aside h2, + aside h3, + aside h4, + aside h5, + aside h6 { + font-size: 1em; + text-transform: none; + letter-spacing: none + } + + aside.big { + background: 0 0; + border: 0; + -webkit-border-start: 1px solid var(--muted-fg); + border-inline-start: 1px solid var(--muted-fg); + border-radius: 0; + padding: 0; + -webkit-padding-start: var(--rhythm); + padding-inline-start: var(--rhythm); + font-style: italic + } + + .\, + .\, + .\, + .\, + .\, + .\, + h1, + h2, + h3, + h4, + h5, + h6 { + -webkit-margin-after: var(--gap); + margin-block-end: var(--gap); + font-family: var(--secondary-font); + -webkit-margin-before: calc(2*var(--gap)); + margin-block-start: calc(2*var(--gap)); + position: relative + } + + .\, + .\, + h1, + h2 { + font-size: 2em; + text-transform: none; + line-height: calc(2*var(--rhythm)); + letter-spacing: 0 + } + + .\, + h2 { + font-size: 1.6em; + line-height: calc(1.5*var(--rhythm)) + } + + .\, + .\, + .\, + .\, + h3, + h4, + h5, + h6 { + font-size: 1.17em; + line-height: calc(1*var(--rhythm)) + } + + .\, + .\, + .\, + h4, + h5, + h6 { + font-size: 1em; + text-transform: none; + letter-spacing: 0; + -webkit-margin-before: var(--gap); + margin-block-start: var(--gap) + } + + h1+h2, + h1:first-child, + h2+h3, + h2:first-child, + h3+h4, + h3:first-child, + h4+h5, + h4:first-child, + h5+h6, + h5:first-child, + h6:first-child { + -webkit-margin-before: var(--gap); + margin-block-start: var(--gap) + } + + h1:target, + h2:target, + h3:target, + h4:target, + h5:target, + h6:target { + outline: 0 + } + + h1:target::before, + h2:target::before, + h3:target::before, + h4:target::before, + h5:target::before, + h6:target::before { + content: ""; + display: block; + position: absolute; + left: -.5em; + width: 4px; + height: 100%; + background: var(--accent) + } + + header { + -webkit-border-after: 1px solid var(--graphical-fg); + border-block-end: 1px solid var(--graphical-fg) + } + + dt, + footer, + header { + font-family: var(--secondary-font) + } + + footer { + -webkit-border-before: 1px solid var(--graphical-fg); + border-block-start: 1px solid var(--graphical-fg) + } + + body>footer, + body>header, + main+footer { + padding: var(--rhythm) calc((100% - var(--eff-line-length))/2) + } + + address { + --density: 0 + } + + dl, + hr, + p { + margin-block: var(--gap) + } + + hr { + color: inherit; + margin-inline: 0; + flex: 0 1 0px; + -webkit-border-start: 1px solid var(--accent); + border-inline-start: 1px solid var(--accent); + block-size: auto; + -webkit-border-before: 1px solid var(--accent); + border-block-start: 1px solid var(--accent); + -webkit-border-after: none; + border-block-end: none; + -webkit-border-end: none; + border-inline-end: none + } + + blockquote, + pre { + line-height: var(--rhythm) + } + + pre { + font-family: var(--mono-font); + tab-size: 2; + margin: var(--gap) 0; + overflow-x: auto; + scrollbar-width: thin; + scrollbar-color: var(--accent) transparent; + font-size: .9em + } + + blockquote { + margin-inline: 0 var(--gap); + padding-inline: var(--gap) 0; + margin-block: var(--gap); + font-size: 1.1em; + font-style: italic; + -webkit-border-start: 1px solid var(--graphical-fg); + border-inline-start: 1px solid var(--graphical-fg); + color: var(--muted-fg) + } + + .italic address, + .italic cite, + .italic dfn, + .italic em, + .italic i, + .italic var, + blockquote address, + blockquote cite, + blockquote dfn, + blockquote em, + blockquote i, + blockquote var, + q address, + q cite, + q dfn, + q em, + q i, + q var { + font-style: normal + } + + blockquote footer { + text-align: right; + text-align: end + } + + ol, + ul { + -webkit-padding-start: var(--rhythm); + padding-inline-start: var(--rhythm) + } + + ol ol, + ol ul, + ul ol, + ul ul { + -webkit-padding-start: var(--gap); + padding-inline-start: var(--gap) + } + + ol[role=list], + ol[role=listbox], + ul[role=list], + ul[role=listbox] { + -webkit-padding-start: 0; + padding-inline-start: 0; + list-style: none + } + + ol { + list-style: decimal + } + + dt { + font-weight: 700 + } + + dd { + -webkit-margin-start: var(--rhythm); + margin-inline-start: var(--rhythm) + } + + li::marker { + font-family: var(--secondary-font) + } + + figure { + max-width: 100%; + margin-inline: 0 + } + + figcaption { + margin-block: var(--gap); + font-family: var(--secondary-font); + color: var(--muted-fg) + } + + main { + max-inline-size: var(--eff-line-length); + inline-size: 100%; + margin-inline: auto + } + + main:first-child { + padding-top: var(--gap) + } + + .\, + a { + color: var(--link-fg, var(--accent)); + font-family: var(--secondary-font); + border-radius: var(--border-radius); + outline-offset: 1px; + background: 0 0; + border: 0; + font-size: 1em; + -webkit-text-decoration: 1px dashed underline; + text-decoration: 1px dashed underline + } + + .list-of-links :is(a, .\) { + text-decoration: none + } + + :is(a, .\):focus, + :is(a, .\):hover { + -webkit-text-decoration: 2px solid underline; + text-decoration: 2px solid underline; + cursor: pointer; + outline: 0 + } + + small[role=note] { + display: block; + float: inline-end; + clear: inline-end; + --sidenote-width: 20ch; + max-inline-size: var(--sidenote-width); + padding-inline: 1.5ch 1ch; + -webkit-margin-end: calc(1em - var(--sidenote-width)); + margin-inline-end: calc(1em - var(--sidenote-width)); + -webkit-margin-after: var(--rhythm); + margin-block-end: var(--rhythm); + font-family: var(--secondary-font); + background: var(--bg); + border: 1px solid transparent; + transition: transform .1s ease-in-out + } + + small[role=note]:focus-within, + small[role=note]:hover { + border: 1px solid var(--graphical-fg); + border-radius: var(--border-radius); + transform: translateX(calc(0px - var(--sidenote-width) + min(var(--gutter-width), var(--sidenote-width)))) + } + + .\, + kbd kbd, + small { + font-size: .8em + } + + del, + s { + color: var(--bad-fg) + } + + caption, + q { + font-style: italic + } + + time { + font-variant-numeric: tabular-nums + } + + code, + kbd, + samp { + font-family: var(--mono-font); + font-style: normal + } + + ins, + samp { + color: var(--ok-fg) + } + + kbd kbd { + display: inline-block; + padding: 0 .3em; + line-height: 1.1em; + background: var(--interactive-bg); + border: 1px outset var(--graphical-fg); + border-block-end-width: 3px; + border-radius: var(--border-radius) + } + + sub { + vertical-align: bottom + } + + sub, + sup { + line-height: 1 + } + + mark { + background: var(--warn-bg); + color: var(--warn-fg) + } + + ins { + background: var(--ok-bg) + } + + del { + background: var(--bad-bg) + } + + audio, + embed, + iframe, + img, + object, + video { + max-inline-size: 100%; + inline-size: max-content; + block-size: auto + } + + caption { + text-align: start; + font-family: var(--secondary-font) + } + + tbody { + border-block: 1px solid var(--faded-fg) + } + + select[multiple], + sup, + td, + th { + vertical-align: top + } + + td:not(:last-child), + th:not(:last-child) { + -webkit-padding-end: var(--rhythm); + padding-inline-end: var(--rhythm) + } + + th { + font-family: var(--secondary-font); + text-align: start + } + + input { + display: block + } + + label input:not([specificity-hack]) { + display: inline; + padding-block: 0 + } + + .\, + button, + input::file-selector-button, + input[type=button], + input[type=reset], + input[type=submit] { + display: inline-block; + padding: 0 calc(var(--rhythm)/4); + vertical-align: middle; + box-sizing: border-box; + font-size: .8rem; + line-height: 1.125em; + font-family: var(--secondary-font); + min-height: var(--rhythm); + background: var(--interactive-bg); + border: 1px solid var(--muted-fg); + box-shadow: 0 2px 4px -2px var(--fg); + border-radius: var(--border-radius); + color: var(--fg); + text-decoration: none; + display: inline-flex; + place-items: center + } + + :is(button, input[type="submit"], input[type="reset"], input[type="button"], input::file-selector-button, .\):focus-visible, + :is(button, input[type="submit"], input[type="reset"], input[type="button"], input::file-selector-button, .\):hover { + filter: brightness(1.1); + box-shadow: 0 3px 6px -2px var(--fg); + text-decoration: none + } + + :is(button, input[type="submit"], input[type="reset"], input[type="button"], input::file-selector-button, .\):active { + box-shadow: none + } + + :is(button, input[type="submit"], input[type="reset"], input[type="button"], input::file-selector-button, .\):where([aria-pressed="true"], [aria-expanded="true"]) { + box-shadow: 0 2px 4px -1px var(--fg) inset; + background: var(--pressed-interactive-bg) + } + + :is(button, input[type="submit"], input[type="reset"], input[type="button"], input::file-selector-button, .\):where([aria-pressed="true"], [aria-expanded="true"]):focus-visible, + :is(button, input[type="submit"], input[type="reset"], input[type="button"], input::file-selector-button, .\):where([aria-pressed="true"], [aria-expanded="true"]):hover { + box-shadow: 0 1px 3px -1px var(--fg) inset + } + + [disabled]:is(button, input[type="submit"], input[type="reset"], input[type="button"], input::file-selector-button, .\) { + color: var(--muted-fg); + box-shadow: none + } + + strong>:is(button, input[type="submit"], input[type="reset"], input[type="button"], input::file-selector-button, .\) { + background: var(--accent); + color: var(--bg); + border: 0; + font-weight: 700 + } + + strong>[disabled]:is(button, input[type="submit"], input[type="reset"], input[type="button"], input::file-selector-button, .\) { + color: var(--muted-accent) + } + + .big:is(button, input[type="submit"], input[type="reset"], input[type="button"], input::file-selector-button, .\) { + min-block-size: calc(1.5*var(--rhythm)); + font-size: 1rem; + padding-inline: calc(.5*var(--rhythm)); + line-height: var(--rhythm) + } + + input:not([type]), + input[type=date], + input[type=datetime-local], + input[type=datetime], + input[type=email], + input[type=month], + input[type=number], + input[type=password], + input[type=search], + input[type=tel], + input[type=text], + input[type=time], + input[type=url], + input[type=week], + select, + textarea { + padding: calc(var(--rhythm)/4); + font-size: 1rem; + line-height: inherit; + font-family: var(--main-font); + background: var(--bg); + color: var(--fg); + border: 1px solid var(--graphical-fg); + border-radius: var(--border-radius); + vertical-align: top + } + + :is(input:not([type]), input[type="text"], input[type="search"], input[type="tel"], input[type="url"], input[type="email"], input[type="password"], input[type="date"], input[type="month"], input[type="week"], input[type="time"], input[type="datetime"], input[type="datetime-local"], input[type="number"], select, textarea):focus-visible { + border: 1px solid var(--accent) + } + + :is(input:not([type]), input[type="text"], input[type="search"], input[type="tel"], input[type="url"], input[type="email"], input[type="password"], input[type="date"], input[type="month"], input[type="week"], input[type="time"], input[type="datetime"], input[type="datetime-local"], input[type="number"], select, textarea)::placeholder { + color: var(--muted-fg); + opacity: 1; + text-align: end + } + + input[type=range] { + width: 100%; + padding: calc(var(--gap)/4) + } + + input[type=color] { + padding: 0; + margin: 0; + height: calc(1.5*var(--rhythm)); + border: 0; + background: 0 0 + } + + input[type=file] { + padding: calc(var(--gap)/4) 0; + font: inherit; + line-height: calc(var(--rhythm)/2) + } + + input[type=file]::file-selector-button { + margin-block: .1em 0; + -webkit-margin-end: 1ch; + margin-inline-end: 1ch + } + + optgroup::before { + color: var(--muted-fg); + font-style: normal + } + + label[for] { + display: block; + padding-block: calc(var(--gap)/4) + } + + fieldset>legend+* { + -webkit-margin-before: 0; + margin-block-start: 0 + } + + details:not(specificity-hack) { + -webkit-padding-before: 0; + padding-block-start: 0 + } + + details:not(specificity-hack):not([open]) { + -webkit-padding-after: 0; + padding-block-end: 0 + } + + summary { + margin: calc(0px - var(--gap)); + margin-bottom: 0; + padding-inline: var(--gap); + font-family: var(--secondary-font); + font-weight: 700; + cursor: pointer + } + + summary:active, + summary:focus-visible { + filter: brightness(.8); + outline: 0 + } + + dialog { + inline-inset: 0; + block-size: -moz-fit-content; + block-size: fit-content; + inline-size: -moz-fit-content; + inline-size: fit-content; + margin: auto !important; + background-color: var(--bg); + color: var(--fg) + } + + dialog[open]::backdrop { + display: block; + background: #000; + opacity: .4; + animation: bg 2s + } + + dialog:not([open]) { + display: none + } + + .box, + .sidebar-layout>header, + [role=menu], + [role=tabpanel], + aside, + details, + dialog, + figure { + margin: var(--gap) 0; + padding: var(--gap); + overflow: clip; + border-radius: var(--border-radius); + background: var(--box-bg); + border: 1px solid var(--graphical-fg) + } + + .titlebar { + margin-inline: calc(0px - var(--gap)); + -webkit-margin-after: calc(0px - var(--gap)); + margin-block-end: calc(0px - var(--gap)); + padding-inline: var(--gap); + font: inherit; + font-family: var(--secondary-font); + font-weight: 700; + translate: 0 calc(-1px - var(--gap)); + background: var(--graphical-fg); + color: var(--bg) + } + + .sub-title, + sub-title { + display: block; + font-weight: 400; + color: var(--muted-fg) + } + + .tool-bar, + [role=toolbar] { + display: flex; + flex-flow: row wrap; + gap: calc(var(--gap)/2) + } + + .tool-bar>*, + [role=toolbar]>* { + margin: 0 + } + + .sidebar-layout header li { + margin-block: calc(.5*var(--gap)) + } + + .breadcrumbs[aria-label] [aria-current=page], + .sidebar-layout header a { + font-weight: 700 + } + + @media (min-width:75ch) { + .sidebar-layout { + display: grid; + grid-template-columns: 25ch auto; + inset: 0 + } + + .sidebar-layout>header { + border-block: none; + -webkit-border-start: none; + border-inline-start: none; + margin: 0 + } + + .sidebar-layout>:nth-child(2) { + overflow: auto; + --full-width: calc(100vw - 25ch); + margin-top: var(--gap) + } + } + + .breadcrumbs[aria-label] { + font-family: var(--secondary-font) + } + + .breadcrumbs[aria-label] ol, + .breadcrumbs[aria-label] ul { + list-style: none; + -webkit-padding-start: 0; + padding-inline-start: 0 + } + + .breadcrumbs[aria-label] li { + display: inline + } + + :is(.breadcrumbs[aria-label] li)+li::before { + content: ' / '; + display: inline + } + + .chip, + .navbar, + chip { + font-family: var(--secondary-font); + background: var(--box-bg) + } + + .chip, + chip { + border: 1px solid var(--accent); + border-radius: calc(var(--rhythm)/2); + padding-inline: calc(var(--rhythm)/2) + } + + .navbar { + padding: var(--rhythm); + -webkit-border-after: 1px solid var(--accent); + border-block-end: 1px solid var(--accent); + overflow-x: auto; + scrollbar-width: thin; + position: sticky; + z-index: 5; + top: 0; + left: 0; + right: 0; + display: flex; + flex-flow: row; + align-items: center; + gap: var(--gap) + } + + .navbar.expanded { + flex-flow: column; + align-items: start; + max-height: 90vh; + overflow-y: auto + } + + .navbar.expanded ul[role=list] { + flex-flow: column + } + + .navbar * { + flex-shrink: 0; + margin-block: 0 + } + + .navbar:not(.expanded) nav>:first-child, + .navbar:not(.expanded)>:first-child { + -webkit-margin-start: auto; + margin-inline-start: auto + } + + .navbar:not(.expanded) nav>:last-child, + .navbar:not(.expanded)>:last-child { + -webkit-margin-end: auto; + margin-inline-end: auto + } + + .navbar hr { + align-self: stretch + } + + .navbar nav ul[role=list] { + display: flex; + flex-flow: row; + gap: var(--rhythm); + -webkit-padding-start: 0; + padding-inline-start: 0 + } + + .navbar nav ul[role=list] * { + flex-shrink: 0 + } + + .navbar a { + font-weight: 700; + text-decoration: none; + padding-inline: .2em + } + + .navbar a:focus, + .navbar a:hover { + text-decoration: underline + } + + .navbar [aria-current=page] { + position: relative + } + + .navbar [aria-current=page]::after { + width: 100%; + height: 6px; + content: ""; + display: block; + position: absolute; + bottom: calc(-1*var(--gap)); + background: currentcolor + } + + .navbar.expanded [aria-current=page]::after { + width: 6px; + height: 100%; + position: absolute; + left: calc(-1*var(--gap)); + top: 0 + } + + .permalink-anchor { + display: none + } + + :hover>.permalink-anchor { + display: initial + } + + button.iconbutton { + border: 0; + background: 0 0; + color: currentcolor; + padding: 0; + line-height: var(--rhythm); + font-size: 24px; + width: 24px; + height: 24px; + display: inline-block; + text-align: center; + border-radius: 50%; + transition: font-weight .2s ease-in-out + } + + button.iconbutton:focus-visible, + button.iconbutton:hover { + outline: 1px solid var(--accent); + outline-offset: 6px + } + + button.iconbutton:active { + outline-offset: 3px; + background: 0 0 + } + + button.iconbutton[aria-pressed=true] { + box-shadow: none; + transform: none + } + + [role=tablist] { + display: flex; + gap: .5ch; + scrollbar-width: thin + } + + [role=tab][role=tab] { + all: initial; + font-family: var(--secondary-font); + padding: 0 calc(var(--rhythm)/4); + margin: 0; + min-height: var(--rhythm); + bottom: -1px; + position: relative; + color: var(--fg); + border: solid var(--graphical-fg); + border-width: 1px; + background: var(--interactive-bg); + border-start-start-radius: .4em; + border-start-end-radius: .4em + } + + [role=tab][role=tab]:active, + [role=tab][role=tab][aria-selected=true] { + background: var(--box-bg); + -webkit-border-after: 1px solid transparent; + border-block-end: 1px solid transparent + } + + [role=tab][role=tab]:hover { + background-color: var(--box-bg); + box-shadow: none + } + + [role=tab][role=tab]:focus-visible { + box-shadow: none; + color: var(--accent); + text-decoration: underline + } + + [role=tabpanel] { + -webkit-margin-before: 0; + margin-block-start: 0; + border-start-start-radius: 0; + border-start-end-radius: 0; + z-index: 1 + } + + [role=menu] { + position: absolute; + z-index: 10; + padding: calc(var(--gap)/2) 0; + margin: 1px 0 0; + display: flex; + flex-flow: column nowrap + } + + [role=menuitem] { + padding: 0 calc(var(--gap)/2); + display: block; + text-decoration: none; + border-radius: 0; + color: var(--fg) + } + + [role=menuitem]:active, + [role=menuitem]:focus { + background: var(--accent); + color: var(--bg) + } + + [role=listbox] { + list-style: none + } + + [role=listbox] [role=option] { + margin-inline: calc(-1*var(--gap)); + padding-inline: var(--gap) + } + + [role=listbox] [role=option][aria-selected=true] { + background: var(--interactive-bg) + } + + [role=listbox] .active[role=option] { + --temporary-bg: var(--accent); + --temporary-fg: var(--bg); + --temporary-accent: parent-var(--muted-accent); + --temporary-muted-accent: parent-var(--box-bg); + background: var(--temporary-bg); + color: var(--temporary-fg) + } + + [role=listbox] .active[role=option]>* { + --bg: var(--temporary-bg); + --fg: var(--temporary-fg); + --accent: var(--temporary-accent); + --muted-accent: var(--temporary-muted-accent) + } + + [aria-orientation=vertical] { + flex-direction: column; + width: -moz-fit-content; + width: fit-content; + text-align: center + } + + .plain { + --box-bg: var(--plain-bg); + --accent: var(--plain-fg); + --graphical-fg: var(--plain-graphical-fg) + } + + .info { + --box-bg: var(--info-bg); + --accent: var(--info-fg); + --graphical-fg: var(--info-graphical-fg) + } + + .ok { + --box-bg: var(--ok-bg); + --accent: var(--ok-fg); + --graphical-fg: var(--ok-graphical-fg) + } + + .warn { + --box-bg: var(--warn-bg); + --accent: var(--warn-fg); + --graphical-fg: var(--warn-graphical-fg) + } + + .bad { + --box-bg: var(--bad-bg); + --accent: var(--bad-fg); + --graphical-fg: var(--bad-graphical-fg) + } + + .color { + color: var(--accent) + } + + .bg { + background: var(--box-bg) + } + + .border { + border-style: solid; + border-color: var(--graphical-fg) + } + + :root { + --fg: var(--gray-12); + --muted-fg: var(--gray-10); + --faded-fg: var(--gray-6); + --graphical-fg: var(--plain-graphical-fg); + --plain-fg: var(--blue-10); + --info-fg: var(--blue-11); + --ok-fg: var(--green-11); + --bad-fg: var(--red-11); + --warn-fg: var(--yellow-11); + --plain-graphical-fg: var(--gray-6); + --info-graphical-fg: var(--blue-6); + --ok-graphical-fg: var(--green-6); + --bad-graphical-fg: var(--red-6); + --warn-graphical-fg: var(--yellow-6); + --bg: var(--gray-0); + --box-bg: var(--plain-bg); + --interactive-bg: var(--gray-4); + --plain-bg: var(--gray-1); + --info-bg: var(--blue-1); + --ok-bg: var(--green-1); + --bad-bg: var(--red-1); + --warn-bg: var(--yellow-1); + --accent: var(--blue-10); + --muted-accent: var(--blue-7); + --rhythm: 1.4rem; + --line-length: 40rem; + --border-radius: .2rem; + --main-font: 'Source Sans 3', 'Source Sans Pro', -apple-system, system-ui, sans-serif; + --secondary-font: var(--main-font); + --mono-font: 'M Plus Code Latin', monospace, monospace; + --density: 1; + --full-width: 100vw; + --eff-line-length: + /* Effective line length for prose. */ + min(calc(var(--full-width) - (2 * var(--rhythm))), + var(--line-length)); + --gutter-width: + /* Width of spaces at each side of page content. */ + calc((var(--full-width) + /* Viewport width */ + - var(--eff-line-length) + /* minus line width */ + ) / 2) + } + + @media (prefers-color-scheme:dark) { + :root:not(.-no-dark-theme) { + --fg: var(--gray-0); + --muted-fg: var(--gray-2); + --faded-fg: var(--gray-7); + --plain-bg: var(--gray-11); + --info-bg: var(--blue-12); + --ok-bg: var(--green-12); + --bad-bg: var(--red-12); + --warn-bg: var(--yellow-12); + --plain-faded-fg: var(--blue-6); + --info-faded-fg: var(--blue-6); + --ok-faded-fg: var(--green-6); + --bad-faded-fg: var(--red-6); + --warn-faded-fg: var(--yellow-6); + --bg: var(--gray-12); + --box-bg: var(--gray-10); + --interactive-bg: var(--gray-8); + --plain-fg: (--blue-3); + --info-fg: var(--blue-3); + --ok-fg: var(--green-3); + --bad-fg: var(--red-3); + --warn-fg: var(--yellow-3); + --accent: var(--blue-3); + --muted-accent: var(--blue-5) + } + } + + * { + --gap: calc(var(--rhythm) * var(--density)); + accent-color: var(--accent) + } + + .textcolumns { + --col-width: 30ch; + column-width: var(--col-width); + column-gap: var(--gap); + margin-block: var(--gap) + } + + .textcolumns :first-child { + -webkit-margin-before: 0 !important; + margin-block-start: 0 !important + } + + .text-align\:center { + text-align: center + } + + .center { + display: grid; + place-items: center + } + + .container { + max-inline-size: var(--eff-line-length); + margin-inline: auto + } + + .fullbleed, + .fullscreen { + position: relative; + left: 50%; + border-radius: 0; + border-inline: none + } + + .fullbleed { + width: var(--full-width); + transform: translateX(calc(-.5*var(--full-width))) + } + + .fullscreen { + height: 100vh; + width: 100vw; + transform: translateX(-50vw) + } + + .width\:100\% { + width: 100%; + max-width: 100% + } + + .height\:100\% { + height: 100%; + max-height: 100% + } + + :is(body, + .box, + [role=menu], + .sidebar-layout > header, + [role=tabpanel], + figure, + details, + dialog, + aside, + fieldset, + dd, + td, + th)>:first-child:first-child:first-child:first-child, + :is(body, + .box, + [role=menu], + .sidebar-layout > header, + [role=tabpanel], + figure, + details, + dialog, + aside, + fieldset, + dd, + td, + th)>:first-child>:first-child:first-child:first-child, + :is(body, + .box, + [role=menu], + .sidebar-layout > header, + [role=tabpanel], + figure, + details, + dialog, + aside, + fieldset, + dd, + td, + th)>:first-child>:first-child>:first-child:first-child, + :is(body, + .box, + [role=menu], + .sidebar-layout > header, + [role=tabpanel], + figure, + details, + dialog, + aside, + fieldset, + dd, + td, + th)>:first-child>:first-child>:first-child>:first-child { + -webkit-margin-before: 0; + margin-block-start: 0 + } + + :is(body, + .box, + [role=menu], + .sidebar-layout > header, + [role=tabpanel], + figure, + details, + dialog, + aside, + fieldset, + dd, + td, + th)>:last-child:last-child:last-child:last-child, + :is(body, + .box, + [role=menu], + .sidebar-layout > header, + [role=tabpanel], + figure, + details, + dialog, + aside, + fieldset, + dd, + td, + th)>:last-child>:last-child:last-child:last-child, + :is(body, + .box, + [role=menu], + .sidebar-layout > header, + [role=tabpanel], + figure, + details, + dialog, + aside, + fieldset, + dd, + td, + th)>:last-child>:last-child>:last-child:last-child, + :is(body, + .box, + [role=menu], + .sidebar-layout > header, + [role=tabpanel], + figure, + details, + dialog, + aside, + fieldset, + dd, + td, + th)>:last-child>:last-child>:last-child>:last-child { + -webkit-margin-after: 0; + margin-block-end: 0 + } + + .padding { + padding-inline: var(--gap) + } + + .padding-block { + padding-block: var(--gap) + } + + .padding-block-start { + -webkit-padding-before: var(--gap); + padding-block-start: var(--gap) + } + + .padding-block-end { + -webkit-padding-after: var(--gap); + padding-block-end: var(--gap) + } + + .padding-inline { + padding-inline: var(--gap) + } + + .padding-inline-end, + .padding-inline-start { + -webkit-padding-start: var(--gap); + padding-inline-start: var(--gap) + } + + .margin { + margin: var(--gap) + } + + .margin-block { + margin-block: var(--gap) + } + + .margin-block-start { + -webkit-margin-before: var(--gap); + margin-block-start: var(--gap) + } + + .margin-block-end { + -webkit-margin-after: var(--gap); + margin-block-end: var(--gap) + } + + .margin-inline { + margin-inline: var(--gap) + } + + .margin-inline-start { + -webkit-margin-start: var(--gap); + margin-inline-start: var(--gap) + } + + .margin-inline-end { + -webkit-margin-end: var(--gap); + margin-inline-end: var(--gap) + } + + .flow-gap>:not(:last-child) { + margin-bottom: 1em + } + + .inline { + display: inline + } + + .block { + display: block + } + + .contents { + display: contents + } + + .table { + display: table; + width: 100%; + margin: 0 + } + + .row, + .rows>* { + display: table-row + } + + .row:not(:last-child):not([specificity-hack])>*, + .rows>:not(:last-child):not([specificity-hack])>* { + margin-bottom: var(--gap) + } + + .row>:not([specificity-hack]), + .rows>*>:not([specificity-hack]) { + display: table-cell; + vertical-align: top + } + + .row>*+:not([specificity-hack]), + :is(.rows > *)>*+:not([specificity-hack]) { + -webkit-margin-start: var(--gap); + margin-inline-start: var(--gap); + display: inline-block + } + + .big { + font-size: 1.4em; + line-height: calc(1.5*var(--rhythm)) + } + + .fixed { + position: fixed + } + + .sticky { + position: sticky + } + + .top { + top: 0 + } + + .right { + right: 0 + } + + .bottom { + bottom: 0 + } + + .left { + left: 0 + } + + .float\:left { + float: left + } + + .float\:right { + float: right + } + + .overflow\:auto { + overflow: auto + } + + .overflow\:scroll { + overflow: scroll + } + + .airy { + --density: 3 + } + + .spacious { + --density: 2 + } + + .dense { + --density: 1 + } + + .crowded { + --density: .5 + } + + .packed { + --density: 0 + } + + .autodensity { + --density: 1 + } + + @media (min-width:768px) { + .autodensity { + --density: 2 + } + } + + @media (min-width:1024px) { + .autodensity { + --density: 3 + } + } + + .vh, + v-h { + clip: rect(0 0 0 0); + -webkit-clip-path: inset(50%); + clip-path: inset(50%); + block-size: 1px; + inline-size: 1px; + overflow: hidden; + white-space: nowrap + } + + .all\:initial { + all: initial + } + + .bold { + font-weight: 700 + } + + .italic { + font-style: italic + } + + .allcaps { + text-transform: uppercase; + letter-spacing: .1rem + } + + .primary-font { + font-family: var(--primary-font) + } + + .secondary-font { + font-family: var(--secondary-font) + } + + .display-font { + font-family: var(--display-font) + } + + .mono-font, + .monospace { + font-family: var(--mono-font) + } + + .massivetext { + font-size: calc(.13*var(--eff-line-length)); + line-height: 1em; + letter-spacing: 0 + } + + .aestheticbreak { + display: block; + margin: 0; + padding: 0; + height: calc(.5*var(--gap)) + } + + .f-row { + display: flex; + flex-direction: row; + gap: var(--gap) + } + + .f-row>* { + margin: 0 + } + + .f-col { + display: flex; + flex-direction: column; + gap: var(--gap) + } + + .f-col>* { + margin: 0 + } + + .f-switch { + display: flex; + flex-wrap: wrap; + gap: var(--gap); + --f-switch-threshold: 55ch + } + + .f-switch>* { + margin: 0; + flex-grow: 1; + flex-basis: calc((var(--f-switch-threshold) - 100%)*999) + } + + .justify-content\:start { + justify-content: start + } + + .justify-content\:end { + justify-content: end + } + + .justify-content\:baseline { + justify-content: baseline + } + + .justify-content\:center { + justify-content: center + } + + .justify-content\:stretch { + justify-content: stretch + } + + .justify-content\:space-between { + justify-content: space-between + } + + .justify-content\:space-around { + justify-content: space-around + } + + .justify-content\:space-evenly { + justify-content: space-evenly + } + + .align-items\:start { + align-items: start + } + + .align-items\:end { + align-items: end + } + + .align-items\:baseline { + align-items: baseline + } + + .align-items\:center { + align-items: center + } + + .align-items\:stretch { + align-items: stretch + } + + .align-self\:start { + align-self: start + } + + .align-self\:end { + align-self: end + } + + .align-self\:baseline { + align-self: baseline + } + + .align-self\:center { + align-self: center + } + + .align-self\:stretch { + align-self: stretch + } + + .flex-grow\:0 { + flex-grow: 0 + } + + .flex-grow\:1 { + flex-grow: 1 + } + + .flex-grow\:2 { + flex-grow: 2 + } + + .flex-grow\:3 { + flex-grow: 3 + } + + .flex-grow\:4 { + flex-grow: 4 + } + + .flex-grow\:5 { + flex-grow: 5 + } + + .flex-grow\:6 { + flex-grow: 6 + } + + .flex-grow\:7 { + flex-grow: 7 + } + + .flex-grow\:8 { + flex-grow: 8 + } + + .flex-grow\:9 { + flex-grow: 9 + } + + .flex-grow\:10 { + flex-grow: 10 + } + + .flex-grow\:11 { + flex-grow: 11 + } + + .flex-grow\:12 { + flex-grow: 12 + } + + .flex-wrap\:wrap { + flex-wrap: wrap + } + + .flex-wrap\:nowrap { + flex-wrap: nowrap + } + + .grid { + display: grid; + grid-auto-columns: var(--grid-col-width, 1fr); + grid-auto-rows: var(--grid-row-width, auto); + gap: var(--gap) + } + + .grid>* { + margin: 0 + } + + .grid-even-rows { + --grid-row-width: 1fr + } + + .grid-variable-cols { + --grid-column-width: auto + } + + [data-cols^="1 "] { + grid-column-start: 1 + } + + [data-cols$=" 1"] { + grid-column-end: 2 + } + + [data-cols="1"] { + grid-column: 1 + } + + [data-cols^="2 "] { + grid-column-start: 2 + } + + [data-cols$=" 2"] { + grid-column-end: 3 + } + + [data-cols="2"] { + grid-column: 2 + } + + [data-cols^="3 "] { + grid-column-start: 3 + } + + [data-cols$=" 3"] { + grid-column-end: 4 + } + + [data-cols="3"] { + grid-column: 3 + } + + [data-cols^="4 "] { + grid-column-start: 4 + } + + [data-cols$=" 4"] { + grid-column-end: 5 + } + + [data-cols="4"] { + grid-column: 4 + } + + [data-cols^="5 "] { + grid-column-start: 5 + } + + [data-cols$=" 5"] { + grid-column-end: 6 + } + + [data-cols="5"] { + grid-column: 5 + } + + [data-cols^="6 "] { + grid-column-start: 6 + } + + [data-cols$=" 6"] { + grid-column-end: 7 + } + + [data-cols="6"] { + grid-column: 6 + } + + [data-cols^="7 "] { + grid-column-start: 7 + } + + [data-cols$=" 7"] { + grid-column-end: 8 + } + + [data-cols="7"] { + grid-column: 7 + } + + [data-cols^="8 "] { + grid-column-start: 8 + } + + [data-cols$=" 8"] { + grid-column-end: 9 + } + + [data-cols="8"] { + grid-column: 8 + } + + [data-cols^="9 "] { + grid-column-start: 9 + } + + [data-cols$=" 9"] { + grid-column-end: 10 + } + + [data-cols="9"] { + grid-column: 9 + } + + [data-cols^="10 "] { + grid-column-start: 10 + } + + [data-cols$=" 10"] { + grid-column-end: 11 + } + + [data-cols="10"] { + grid-column: 10 + } + + [data-cols^="11 "] { + grid-column-start: 11 + } + + [data-cols$=" 11"] { + grid-column-end: 12 + } + + [data-cols="11"] { + grid-column: 11 + } + + [data-cols^="12 "] { + grid-column-start: 12 + } + + [data-cols$=" 12"] { + grid-column-end: 13 + } + + [data-cols="12"] { + grid-column: 12 + } + + [data-rows^="1 "] { + grid-row-start: 1 + } + + [data-rows$=" 1"] { + grid-row-end: 2 + } + + [data-rows="1"] { + grid-row: 1 + } + + [data-rows^="2 "] { + grid-row-start: 2 + } + + [data-rows$=" 2"] { + grid-row-end: 3 + } + + [data-rows="2"] { + grid-row: 2 + } + + [data-rows^="3 "] { + grid-row-start: 3 + } + + [data-rows$=" 3"] { + grid-row-end: 4 + } + + [data-rows="3"] { + grid-row: 3 + } + + [data-rows^="4 "] { + grid-row-start: 4 + } + + [data-rows$=" 4"] { + grid-row-end: 5 + } + + [data-rows="4"] { + grid-row: 4 + } + + [data-rows^="5 "] { + grid-row-start: 5 + } + + [data-rows$=" 5"] { + grid-row-end: 6 + } + + [data-rows="5"] { + grid-row: 5 + } + + [data-rows^="6 "] { + grid-row-start: 6 + } + + [data-rows$=" 6"] { + grid-row-end: 7 + } + + [data-rows="6"] { + grid-row: 6 + } + + [data-rows^="7 "] { + grid-row-start: 7 + } + + [data-rows$=" 7"] { + grid-row-end: 8 + } + + [data-rows="7"] { + grid-row: 7 + } + + [data-rows^="8 "] { + grid-row-start: 8 + } + + [data-rows$=" 8"] { + grid-row-end: 9 + } + + [data-rows="8"] { + grid-row: 8 + } + + [data-rows^="9 "] { + grid-row-start: 9 + } + + [data-rows$=" 9"] { + grid-row-end: 10 + } + + [data-rows="9"] { + grid-row: 9 + } + + [data-rows^="10 "] { + grid-row-start: 10 + } + + [data-rows$=" 10"] { + grid-row-end: 11 + } + + [data-rows="10"] { + grid-row: 10 + } + + [data-rows^="11 "] { + grid-row-start: 11 + } + + [data-rows$=" 11"] { + grid-row-end: 12 + } + + [data-rows="11"] { + grid-row: 11 + } + + [data-rows^="12 "] { + grid-row-start: 12 + } + + [data-rows$=" 12"] { + grid-row-end: 13 + } + + [data-rows="12"] { + grid-row: 12 + } + + @media (max-width:768px) { + [data-cols\@s^="1 "] { + grid-column-start: 1 + } + + [data-cols\@s$=" 1"] { + grid-column-end: 2 + } + + [data-cols\@s="1"] { + grid-column: 1 + } + + [data-cols\@s^="2 "] { + grid-column-start: 2 + } + + [data-cols\@s$=" 2"] { + grid-column-end: 3 + } + + [data-cols\@s="2"] { + grid-column: 2 + } + + [data-cols\@s^="3 "] { + grid-column-start: 3 + } + + [data-cols\@s$=" 3"] { + grid-column-end: 4 + } + + [data-cols\@s="3"] { + grid-column: 3 + } + + [data-cols\@s^="4 "] { + grid-column-start: 4 + } + + [data-cols\@s$=" 4"] { + grid-column-end: 5 + } + + [data-cols\@s="4"] { + grid-column: 4 + } + + [data-cols\@s^="5 "] { + grid-column-start: 5 + } + + [data-cols\@s$=" 5"] { + grid-column-end: 6 + } + + [data-cols\@s="5"] { + grid-column: 5 + } + + [data-cols\@s^="6 "] { + grid-column-start: 6 + } + + [data-cols\@s$=" 6"] { + grid-column-end: 7 + } + + [data-cols\@s="6"] { + grid-column: 6 + } + + [data-cols\@s^="7 "] { + grid-column-start: 7 + } + + [data-cols\@s$=" 7"] { + grid-column-end: 8 + } + + [data-cols\@s="7"] { + grid-column: 7 + } + + [data-cols\@s^="8 "] { + grid-column-start: 8 + } + + [data-cols\@s$=" 8"] { + grid-column-end: 9 + } + + [data-cols\@s="8"] { + grid-column: 8 + } + + [data-cols\@s^="9 "] { + grid-column-start: 9 + } + + [data-cols\@s$=" 9"] { + grid-column-end: 10 + } + + [data-cols\@s="9"] { + grid-column: 9 + } + + [data-cols\@s^="10 "] { + grid-column-start: 10 + } + + [data-cols\@s$=" 10"] { + grid-column-end: 11 + } + + [data-cols\@s="10"] { + grid-column: 10 + } + + [data-cols\@s^="11 "] { + grid-column-start: 11 + } + + [data-cols\@s$=" 11"] { + grid-column-end: 12 + } + + [data-cols\@s="11"] { + grid-column: 11 + } + + [data-cols\@s^="12 "] { + grid-column-start: 12 + } + + [data-cols\@s$=" 12"] { + grid-column-end: 13 + } + + [data-cols\@s="12"] { + grid-column: 12 + } + + [data-rows\@s^="1 "] { + grid-row-start: 1 + } + + [data-rows\@s$=" 1"] { + grid-row-end: 2 + } + + [data-rows\@s="1"] { + grid-row: 1 + } + + [data-rows\@s^="2 "] { + grid-row-start: 2 + } + + [data-rows\@s$=" 2"] { + grid-row-end: 3 + } + + [data-rows\@s="2"] { + grid-row: 2 + } + + [data-rows\@s^="3 "] { + grid-row-start: 3 + } + + [data-rows\@s$=" 3"] { + grid-row-end: 4 + } + + [data-rows\@s="3"] { + grid-row: 3 + } + + [data-rows\@s^="4 "] { + grid-row-start: 4 + } + + [data-rows\@s$=" 4"] { + grid-row-end: 5 + } + + [data-rows\@s="4"] { + grid-row: 4 + } + + [data-rows\@s^="5 "] { + grid-row-start: 5 + } + + [data-rows\@s$=" 5"] { + grid-row-end: 6 + } + + [data-rows\@s="5"] { + grid-row: 5 + } + + [data-rows\@s^="6 "] { + grid-row-start: 6 + } + + [data-rows\@s$=" 6"] { + grid-row-end: 7 + } + + [data-rows\@s="6"] { + grid-row: 6 + } + + [data-rows\@s^="7 "] { + grid-row-start: 7 + } + + [data-rows\@s$=" 7"] { + grid-row-end: 8 + } + + [data-rows\@s="7"] { + grid-row: 7 + } + + [data-rows\@s^="8 "] { + grid-row-start: 8 + } + + [data-rows\@s$=" 8"] { + grid-row-end: 9 + } + + [data-rows\@s="8"] { + grid-row: 8 + } + + [data-rows\@s^="9 "] { + grid-row-start: 9 + } + + [data-rows\@s$=" 9"] { + grid-row-end: 10 + } + + [data-rows\@s="9"] { + grid-row: 9 + } + + [data-rows\@s^="10 "] { + grid-row-start: 10 + } + + [data-rows\@s$=" 10"] { + grid-row-end: 11 + } + + [data-rows\@s="10"] { + grid-row: 10 + } + + [data-rows\@s^="11 "] { + grid-row-start: 11 + } + + [data-rows\@s$=" 11"] { + grid-row-end: 12 + } + + [data-rows\@s="11"] { + grid-row: 11 + } + + [data-rows\@s^="12 "] { + grid-row-start: 12 + } + + [data-rows\@s$=" 12"] { + grid-row-end: 13 + } + + [data-rows\@s="12"] { + grid-row: 12 + } + } + + @media (min-width:1024px) { + [data-cols\@l^="1 "] { + grid-column-start: 1 + } + + [data-cols\@l$=" 1"] { + grid-column-end: 2 + } + + [data-cols\@l="1"] { + grid-column: 1 + } + + [data-cols\@l^="2 "] { + grid-column-start: 2 + } + + [data-cols\@l$=" 2"] { + grid-column-end: 3 + } + + [data-cols\@l="2"] { + grid-column: 2 + } + + [data-cols\@l^="3 "] { + grid-column-start: 3 + } + + [data-cols\@l$=" 3"] { + grid-column-end: 4 + } + + [data-cols\@l="3"] { + grid-column: 3 + } + + [data-cols\@l^="4 "] { + grid-column-start: 4 + } + + [data-cols\@l$=" 4"] { + grid-column-end: 5 + } + + [data-cols\@l="4"] { + grid-column: 4 + } + + [data-cols\@l^="5 "] { + grid-column-start: 5 + } + + [data-cols\@l$=" 5"] { + grid-column-end: 6 + } + + [data-cols\@l="5"] { + grid-column: 5 + } + + [data-cols\@l^="6 "] { + grid-column-start: 6 + } + + [data-cols\@l$=" 6"] { + grid-column-end: 7 + } + + [data-cols\@l="6"] { + grid-column: 6 + } + + [data-cols\@l^="7 "] { + grid-column-start: 7 + } + + [data-cols\@l$=" 7"] { + grid-column-end: 8 + } + + [data-cols\@l="7"] { + grid-column: 7 + } + + [data-cols\@l^="8 "] { + grid-column-start: 8 + } + + [data-cols\@l$=" 8"] { + grid-column-end: 9 + } + + [data-cols\@l="8"] { + grid-column: 8 + } + + [data-cols\@l^="9 "] { + grid-column-start: 9 + } + + [data-cols\@l$=" 9"] { + grid-column-end: 10 + } + + [data-cols\@l="9"] { + grid-column: 9 + } + + [data-cols\@l^="10 "] { + grid-column-start: 10 + } + + [data-cols\@l$=" 10"] { + grid-column-end: 11 + } + + [data-cols\@l="10"] { + grid-column: 10 + } + + [data-cols\@l^="11 "] { + grid-column-start: 11 + } + + [data-cols\@l$=" 11"] { + grid-column-end: 12 + } + + [data-cols\@l="11"] { + grid-column: 11 + } + + [data-cols\@l^="12 "] { + grid-column-start: 12 + } + + [data-cols\@l$=" 12"] { + grid-column-end: 13 + } + + [data-cols\@l="12"] { + grid-column: 12 + } + + [data-rows\@l^="1 "] { + grid-row-start: 1 + } + + [data-rows\@l$=" 1"] { + grid-row-end: 2 + } + + [data-rows\@l="1"] { + grid-row: 1 + } + + [data-rows\@l^="2 "] { + grid-row-start: 2 + } + + [data-rows\@l$=" 2"] { + grid-row-end: 3 + } + + [data-rows\@l="2"] { + grid-row: 2 + } + + [data-rows\@l^="3 "] { + grid-row-start: 3 + } + + [data-rows\@l$=" 3"] { + grid-row-end: 4 + } + + [data-rows\@l="3"] { + grid-row: 3 + } + + [data-rows\@l^="4 "] { + grid-row-start: 4 + } + + [data-rows\@l$=" 4"] { + grid-row-end: 5 + } + + [data-rows\@l="4"] { + grid-row: 4 + } + + [data-rows\@l^="5 "] { + grid-row-start: 5 + } + + [data-rows\@l$=" 5"] { + grid-row-end: 6 + } + + [data-rows\@l="5"] { + grid-row: 5 + } + + [data-rows\@l^="6 "] { + grid-row-start: 6 + } + + [data-rows\@l$=" 6"] { + grid-row-end: 7 + } + + [data-rows\@l="6"] { + grid-row: 6 + } + + [data-rows\@l^="7 "] { + grid-row-start: 7 + } + + [data-rows\@l$=" 7"] { + grid-row-end: 8 + } + + [data-rows\@l="7"] { + grid-row: 7 + } + + [data-rows\@l^="8 "] { + grid-row-start: 8 + } + + [data-rows\@l$=" 8"] { + grid-row-end: 9 + } + + [data-rows\@l="8"] { + grid-row: 8 + } + + [data-rows\@l^="9 "] { + grid-row-start: 9 + } + + [data-rows\@l$=" 9"] { + grid-row-end: 10 + } + + [data-rows\@l="9"] { + grid-row: 9 + } + + [data-rows\@l^="10 "] { + grid-row-start: 10 + } + + [data-rows\@l$=" 10"] { + grid-row-end: 11 + } + + [data-rows\@l="10"] { + grid-row: 10 + } + + [data-rows\@l^="11 "] { + grid-row-start: 11 + } + + [data-rows\@l$=" 11"] { + grid-row-end: 12 + } + + [data-rows\@l="11"] { + grid-row: 11 + } + + [data-rows\@l^="12 "] { + grid-row-start: 12 + } + + [data-rows\@l$=" 12"] { + grid-row-end: 13 + } + + [data-rows\@l="12"] { + grid-row: 12 + } + } + + @page { + size: 8.25in 11in; + margin: 1in 1.1in 1in 1.1in; + + @footnote { + float: bottom; + border-top: 1px solid var(--faded-fg); + } + } + + @page :left { + @top-left { + font-family: var(--secondary-font); + content: counter(page); + } + } + + @page :right { + @top-right { + font-family: var(--secondary-font); + content: counter(page); + } + } + + @page :empty { + @top-right { + content: ''; + } + + @top-left { + content: ''; + } + } + + @page :nth(1) { + @top-right { + content: ''; + } + + @top-left { + content: ''; + } + } + + :root { + --main-font: 'Andada Pro', 'Noto Serif', serif; + --secondary-font: 'Alegreya Sans', sans-serif; + --display-font: 'ChicagoFLF'; + --mono-font: 'Berkeley Mono Variable'; + + hyphens: auto; + -webkit-hyphens: auto; + } + + h1, + h2, + h3, + h4, + h5, + h6 { + font-family: var(--display-font); + page-break-after: avoid; + } + + pre { + font-size: .8em; + } + + code:not(pre code) { + font-size: .8em; + } + + .language-asciiart { + line-height: 1; + } + + h1:not(.massivetext) { + margin-top: 60%; + page-break-after: always; + page-break-before: right; + } + + h2 { + margin-top: 20%; + page-break-before: right; + } + + .cover { + page: title; + page-break-after: always; + } + + .attribution { + padding-inline-start: var(--gap); + page-break-before: avoid; + } + + .footnote { + float: footnote; + font-size: .8em; + } + + [data-footnote-call]::after { + content: counter(footnote, symbols('*', '†', '‡', '§')); + } + + .pagedjs_area a[data-footnote-call] { + cursor: pointer; + } + + [data-footnote-marker]::after { + content: counter(footnote, symbols('*', '†', '‡', '§')) '.'; + } + + .pagedjs_page { + counter-reset: footnote; + } + + a { + color: currentColor; + font: inherit; + text-decoration: inherit; + } + + p { + text-align: justify; + } + + .sect1 { + page-break-before: always; + } + + .title { + font-family: var(--secondary-font); + } + + pre { + overflow: visible; + white-space: pre-wrap; + } + + .title:last-child { + page-break-before: avoid; + } + + .title:first-child { + page-break-after: avoid; + } + + + +
-

Hypermedia Systems

-

Adam Stepinski, Carson Gross, Deniz Akşimşek

+

Hypermedia Systems

+

Adam Stepinski, Carson Gross, Deniz Akşimşek

Hypermedia Concepts

-
-

Introduction

-
-
-

This is a book about building applications using hypermedia systems. Hypermedia systems might seem like a strange phrase: -how is hypermedia a system? Isn’t hypermedia just a way to link documents together?

-
-
-

Like with HTML, on the World Wide Web?

-
-
-

What do you mean hypermedia systems?

-
-
-

Well, yes, HTML is a hypermedia. But there is more to the way the web works than just HTML: HTTP, the Hyper Text -Transfer Protocol, is what transfers HTML from servers to clients, and there are many details and features associated -with it: caching, various headers, response codes, and so forth.

-
-
-

And then, of course, there are hypermedia servers, which present hypermedia APIs (yes, APIs) to clients over the network.

-
-
-

And, finally, there is the all-important hypermedia client: a software client that understands how to render a hypermedia -response intelligibly to a human, so that a human can interact with the remote system. The most widely known and used -hypermedia clients are, of course, web browsers.

-
-
-

Web browsers are perhaps the most sophisticated pieces of software we use. They not only understand HTML, CSS and many -other file formats, but they also provide a JavaScript runtime and programming environment that is so powerful that web -developers can create entire applications in it that are nearly as sophisticated as thick clients, that is, native -applications.

-
-
-

This JavaScript runtime is so powerful, in fact, that today many developers ignore the hypermedia features of the -browser, in favor of building their web applications entirely in JavaScript. Applications built in this manner have come -to be called Single Page Applications (SPAs). Rather than navigating between pages, these web applications use -JavaScript for updating the user interface directly. When they communicate with a server, these applications -typically use JSON API calls via AJAX. And they often update the user interface using a “reactive” style front-end -JavaScript library.

-
-
-

In these applications HTML becomes a (somewhat awkward) graphical interface description language that is used -because, for historical reasons, that’s what happens to be there, in the browser.

-
-
-

Applications built in this style are not hypermedia driven: they do not take advantage of the underlying hypermedia -system of the web.

-
-
-

To explain what a hypermedia driven application looks like, and to contrast it with the popular SPA approach of today, -we need to first explore the entire hypermedia system of the web, beyond just discussing HTML. We need to look at the -network architecture of the original web, including how a web server delivers a hypermedia API, and how to effectively -use the hypermedia features available in the hypermedia client (e.g. the browser).

-
-
-

Each of these are important aspects of building an effective hypermedia driven application, and it is the entire -hypermedia system that comes together to make hypermedia such a powerful architecture.

-
-

What is a Hypermedia System?

-
-

To understand what a hypermedia system is we’ll first take an in-depth look at the canonical hypermedia system: the -World Wide Web. Roy Fielding, an engineer who helped create specifications and build the -implementations of many early pieces of the web, gave us the term REpresentational State Transfer, or REST. -In his PhD dissertation he described REST as a network architecture, and he contrasted it with earlier approaches to building -distributed software.

-
-
-

We define a hypermedia system as a system that adheres to the RESTful network architecture in Fielding’s original -sense of this term.

-
-
-

Unfortunately, today, you probably associate the term "REST" with JSON APIs, since that is where the term is typically -used in industry. This is a misapplied use of the term REST because JSON is not a natural hypermedia due to the absense of -hypermedia controls. The exhange of hypermedia is an explicit requirement for a system to be considered "RESTful." -It is a long story how we got here, using the term REST so incorrectly, and we will go into the details later in this book. -But, for now, if you currently think "REST == JSON", please try to set that understanding aside while reading this book, -and come to the concept with fresh eyes.

-
-
-

It is important to understand that, in his dissertation, Fielding was describing The World Wide Web as it existed in the -late 1990s. The web, at that point, was simply web browsers exchanging hypermedia. That system, with its simple links -and forms, was what Fielding was calling RESTful.

-
-
-

JSON APIs were a decade away from becoming a common tool in web development: REST was about hypermedia and the Web 1.0.

-
-

Hypermedia-Driven Applications

-
-

In this book we are going to take a look at hypermedia as a system architecture and then explore some practical, -modern approaches to building web applications using it. We will call applications built in this style -Hypermedia-Driven Applications, or HDAs, and we contrast them with a popular style in use today, the Single Page -Application.

-
-
-

A Hypermedia-Driven Application is an application built on top of a hypermedia system that respects and utilizes the -hypermedia functionality of that underlying system.

-
-

Goals

-
-

The goal of this book is to give you a strong sense of how the RESTful, hypermedia system architecture differs from -other client-server systems, and what the strengths (and weaknesses) of the hypermedia approach are. Further, we hope -to convince you that the hypermedia architecture is relevant to developers building modern web applications.

-
-
-

We aim to give you the tools to evaluate the requirements for an application and answer the question:

-
-
-

“Could I build this as a Hypermedia-Driven Application?”

-
-
-

We hope that for many applications the answer to that question will be “Yes!”

-
-

Book Layout

-
-

The book is broken into three parts:

-
-
-
    -
  • -

    An introduction (or re-introduction) to hypermedia, with a particular focus on HTML and HTTP. We will finish this -review of core hypermedia concepts by creating a simple "Web 1.0"-style application, Contact.app, for managing contacts.

    -
  • -
  • -

    Next we will look at how we can use htmx, a hypermedia-oriented JavaScript library created by the -authors of this book, to improve Contact.app. By using htmx, we will be able to achieve a level of interactivity in our -application that many developers would expect to require a large, sophisticated front end library, such as React. -Thanks to htmx, we will be able to do this without abandoning hypermedia as a system architecture.

    -
  • -
  • -

    Finally, we will look at a completely different hypermedia system, Hyperview. Hyperview is a mobile hypermedia system, related to, but distinct from the web and created by one of the authors of this book — Adam Stepinski. It supports mobile specific features by providing not only a mobile specific hypermedia, but also a mobile hypermedia client, a network protocol and so on. It provides a full mobile hypermedia system for you to build your mobile application with, and, in doing so, makes it possible to build mobile Hypermedia-Driven Applications.

    -
  • -
-
-
-

Note that each section is somewhat independent of the others. If you already know hypermedia in-depth and how basic Web -1.0 applications function, you may want to skip ahead to the second section on htmx and how to build modern web applications -using hypermedia. Similarly, if you are well versed in htmx and want to dive into a novel mobile hypermedia, -you can skip ahead to the Hyperview section.

-
-
-

That being said, the book is designed to be read in order and both the htmx and Hyperview sections build on the Web 1.0 -application described at the end of the first section. Furthermore, even if you are well versed in all the concepts -of hypermedia and details of HTML & HTTP, it is likely worth it to at least skim through the first few chapters for -a refresher.

-
-

Hypermedia: A New Generation

-
-

Hypermedia isn’t a frequent topic of discussion these days. Even many older programmers who grew up with the web -in the late 1990s and early 2000s haven’t thought much about these ideas in years. Many younger web developers have -grown up knowing nothing but Single Page Applications and the frameworks that are used to build them.

-
-
-

In particular, many young web developers began their careers by building React.js applications that interact with a Node server using a -JSON API; they may never have learned about hypermedia as a system at all.

-
-
-

This is a tragedy, and, frankly, a failure on the part of the thought leaders in the web development community to properly -communicate and advocate for the hypermedia approach.

-
-
-

Hypermedia was a great idea! It still is!

-
-
-

By the end of this book, you will have the tools and the language to put this great idea to work in your own -applications. And, further, you will be able to bring the ideas and concepts of hypermedia systems -to the broader web development community.

-
-
-

Hypermedia can compete, hypermedia can win, hypermedia has won as an architectural choice against the Single -Page Application approach, but only if smart people (like you) learn about it, build with it and then tell the world -about it.

-
-
-
-
-

Remember the message? “The future is not set. There is no fate but what we make for ourselves.”

-
-
-
-— Kyle Reese
-Terminator 2: Judgement Day -
-
-
-
-
-

1. Hypermedia: A Reintroduction

-
-
-

Hypermedia is a universal technology today, almost as common as electricity.

-
-
-

Billions of people use hypermedia-based systems every day, mainly by interacting with the Hypertext Markup Language -(HTML) being exchanged via the Hypertext Transfer Protocol (HTTP) by using a web browser connected to the World Wide Web.

-
-
-

People use these systems to get their news, check in on friends, buy things online, play games, send emails and so -forth: the variety and sheer number of online services being delivered by hypermedia is truly astonishing.

-
-
-

And yet, despite this ubiquity, the topic of hypermedia itself is a strangely under-explored concept today, left mainly to -specialists. Yes, you can find a lot of tutorials on how to author HTML, create links and forms, etc. But it is rare -to see a discussion of HTML as a hypermedia and, more broadly, on how an entire hypermedia system fits together.

-
-
-

This is in contrast with the early web development era when concepts like Representational State Transfer (REST) -and Hypermedia As The Engine of Application State (HATEOAS) were discussed frequently, refined and debated among -web developers.

-
-
-

In a sad turn of events, today, the world’s most popular hypermedia, HTML, is often viewed resentfully: it is an -awkward, legacy markup language that must be grudgingly used to build user interfaces in what are -increasingly entirely JavaScript-based web applications.

-
-
-

HTML happens to be there, in the browser, and so we have to use it.

-
-
-

This is a shame and we hope to convince you that hypermedia is not simply a -piece of legacy technology that we have to accept and deal with. Instead, we aim to show you that hypermedia is a -tremendously innovative, simple and flexible way to build robust applications: Hypermedia-Driven Applications.

-
-
-

We hope that by the end of this book you will feel, as we do, that the hypermedia approach deserves a seat at the table -when you, a web developer, are considering the architecture of your next application. Creating a Hypermedia-Driven -Application on top of a hypermedia system like the web is a viable and, indeed, often excellent choice for -modern web applications.

-
-
-

(And, as the section on Hyperview will show, not just web applications.)

-
-
-

1.1. What Is Hypermedia?

-
-
-
-

Hypertexts: new forms of writing, appearing on computer screens, that will branch or perform at the reader’s -command. A hypertext is a non-sequential piece of writing; only the computer display makes it practical.

-
-
-
-— Ted Nelson
-https://archive.org/details/SelectedPapers1977/page/n7/mode/2up -
-
-
-

Let us begin at the beginning: what is hypermedia?

-
-
-

Hypermedia is a media, for example a text, that includes non-linear branching from one location in the media to another, -via, for example, hyperlinks embedded in the media. The prefix “hyper-” derives from the Greek prefix “ὑπερ-” which -means “beyond” or “over”, indicating that hypermedia goes beyond normal, passively consumed media like magazines and -newspapers.

-
-
-

Hyperlinks are a canonical example of what is called a hypermedia control:

-
-
-
-
Hypermedia Control
-
-

A hypermedia control is an element in a hypermedia that describes (or controls) some sort of -interaction, often with a remote server, by encoding information about that interaction directly and completely within -itself.

-
-
-
-
-

Hypermedia controls are what differentiate hypermedia from other sorts of media.

-
-
-

You may be more familiar with the term hypertext, from whose Wikipedia page the above quote is taken. Hypertext -is a sub-category of hypermedia and much of this book is going to discuss how to build modern applications using -hypertexts such as HTML, the Hypertext Markup Language, or HXML, a hypertext used by the Hyperview mobile hypermedia -system.

-
-
-

Hypertexts like HTML function alongside other technologies crucial for making an entire hypermedia system work: network -protocols like HTTP, other media types such as images, videos, hypermedia servers (i.e. servers providing hypermedia APIs), -sophisticated hypermedia clients (e.g. web browsers), and so on.

-
-
-

Because of this, we prefer the broader term hypermedia systems when describing the underlying architecture of -applications built using hypertext, to emphasize the system architecture over the particular hypermedia being used.

-
-
-

It is the entire hypermedia system architecture that is underappreciated and ignored by many modern web developers.

-
-
-
-

1.2. A Brief History of Hypermedia

-
-

Where did the idea of hypermedia come from?

-
-
-

While there were many precursors to the modern idea of hypertext and the more general hypermedia, many people point -to the 1945 article As We May Think written by Vannevar Bush in The Atlantic as a starting point for looking at what -has become modern hypermedia.

-
-
-

In this article Bush described a device called a Memex, which, using a complex mechanical system of reels and microfilm, -along with an encoding system, would allow users to jump between related frames of content. The Memex was never actually -implemented, but it was an inspiration for later work on the idea of hypermedia.

-
-
-

The terms “hypertext” and “hypermedia” were coined in 1963 by Ted Nelson, who would go on to work on the Hypertext Editing -System at Brown University and who later created the File Retrieval and Editing System (FRESS), a shockingly advanced -hypermedia system for its time. (This was perhaps the first digital system to have a notion of “undo”.)

-
-
-

While Nelson was working on his ideas, Douglas Engelbart was busy at work at the Stanford Research Institute, explicitly -attempting to make Vannevar Bush’s Memex a reality. In 1968, Englebart gave “The Mother of All Demos” in San Francisco, -California.

-
-
-

Englebart demonstrated an unbelievable amount of technology:

-
-
-
    -
  • -

    Remote, collaborative text editing with his peers in Menlo Park.

    -
  • -
  • -

    Video and audio chat.

    -
  • -
  • -

    An integrated windowing system, with window resizing, etc.

    -
  • -
  • -

    A recognizable hypertext, whereby clicking on underlined text navigated to new content.

    -
  • -
-
-
-

Despite receiving a standing ovation from a shocked audience after his talk, it was decades before the technologies -Englebart demonstrated became mainstream.

-
-
-

Modern Implementation

-
-

In 1990, Tim Berners-Lee, working at CERN, published the first website. He had been working on the idea of hypertext -for a decade and had finally, out of desperation at the fact it was so hard for researchers to share their research, -found the right moment and institutional support to create the World Wide Web:

-
-
-
-
-

Creating the web was really an act of desperation, because the situation without it was very difficult when I was working -at CERN later. Most of the technology involved in the web, like the hypertext, like the Internet, multifont text objects, had all -been designed already. I just had to put them together. It was a step of generalising, going to a higher level of abstraction, -thinking about all the documentation systems out there as being possibly part of a larger imaginary documentation system.

-
-
-
-— Tim Berners-Lee
-https://britishheritage.org/tim-berners-lee-the-world-wide-web -
-
-
-

By 1994 his creation was taking off so quickly that Berners-Lee founded the W3C, a working group of companies and researchers -tasked with improving the web. All standards created by the W3C were royalty-free and could be adopted and implemented -by anyone, cementing the open, collaborative nature of the web.

-
-
-

In 2000, Roy Fielding, then at U.C. Irvine, published a seminal PhD dissertation on the web: “Architectural Styles and the -Design of Network-based Software Architectures.” Fielding had been working on the open source Apache HTTP Server and -his thesis was a description of what he felt was a new and distinct networking architecture that had emerged in the early -web. Fielding had worked on the initial HTTP specifications and, in the paper, defined the web’s hypermedia -network model using the term REpresentational State Transfer (REST).

-
-
-

Fielding’s work became a major touchstone for early web developers, giving them a language to discuss the new technical -medium they were building applications in.

-
-
-

We will discuss Fielding’s key ideas in depth in Chapter 3, and try to correct the record with respect to REST, -HATEOAS and hypermedia.

-
-
-
-
-

1.3. The World’s Most Successful Hypertext: HTML

-
-
-
-

In the beginning was the hyperlink, and the hyperlink was with the web, and the hyperlink was the web. And it was good.

-
-
-
-— Rescuing REST From the API Winter
-https://intercoolerjs.org/2016/01/18/rescuing-rest.html -
-
-
-

The system that Berners-Lee, Fielding and many others had created revolved around a hypermedia: HTML. HTML started as a read-only -hypermedia, used to publish (at first) academic documents. These documents were linked together via anchor tags which -created hyperlinks between them, allowing users to quickly navigate between documents.

-
-
-

When HTML 2.0 was released, it introduced the notion of the form tag, joining the anchor tag (i.e. hyperlink) as a -second hypermedia control. The introduction of the form tag made building applications on the web viable by providing -a mechanism for updating resources, rather than just reading them.

-
-
-

It was at this point that the web transitioned from an interesting document-oriented system to a compelling -application architecture.

-
-
-

Today HTML is the most widely used hypermedia in existence and this book naturally assumes that the reader has a -reasonable familiarity with it. You do not need to be an HTML (or CSS) expert to understand the code in this book, but -the better you understand the core tags and concepts of HTML, the more you will get out of it.

-
-
-

The Essence of HTML as a Hypermedia

-
-

Let us consider these two defining hypermedia elements (that is the two defining hypermedia controls) of HTML, -the anchor tag and the form tag, in a bit of detail.

-
-
-
Anchor Tags
-
-

Anchor tags are so familiar as to be boring but, as the original hypermedia control, it is worth reviewing the mechanics -of hyperlinks to get our minds in the right place for developing a deeper understanding of hypermedia.

-
-
-

Consider a simple anchor tag, embedded within a larger HTML document:

-
-
-
A Simple Hyperlink
-
-
<a href="https://hypermedia.systems/">
+    
+

Introduction

+
+
+

This is a book about building applications using hypermedia systems. Hypermedia systems + might seem like a strange phrase: + how is hypermedia a system? Isn’t hypermedia just a way to link documents together? +

+
+
+

Like with HTML, on the World Wide Web?

+
+
+

What do you mean hypermedia systems?

+
+
+

Well, yes, HTML is a hypermedia. But there is more to the way the web works than just HTML: + HTTP, the Hyper Text + Transfer Protocol, is what transfers HTML from servers to clients, and there are many details and + features associated + with it: caching, various headers, response codes, and so forth.

+
+
+

And then, of course, there are hypermedia servers, which present hypermedia APIs + (yes, APIs) to clients over the network.

+
+
+

And, finally, there is the all-important hypermedia client: a software client that + understands how to render a hypermedia + response intelligibly to a human, so that a human can interact with the remote system. The + most widely known and used + hypermedia clients are, of course, web browsers.

+
+
+

Web browsers are perhaps the most sophisticated pieces of software we use. They not only understand + HTML, CSS and many + other file formats, but they also provide a JavaScript runtime and programming environment that is + so powerful that web + developers can create entire applications in it that are nearly as sophisticated as thick + clients, that is, native + applications.

+
+
+

This JavaScript runtime is so powerful, in fact, that today many developers ignore the + hypermedia features of the + browser, in favor of building their web applications entirely in JavaScript. Applications built in + this manner have come + to be called Single Page Applications (SPAs). Rather than navigating between pages, these web + applications use + JavaScript for updating the user interface directly. When they communicate with a server, these + applications + typically use JSON API calls via AJAX. And they often update the user interface using a + “reactive” style front-end + JavaScript library.

+
+
+

In these applications HTML becomes a (somewhat awkward) graphical interface description language that + is used + because, for historical reasons, that’s what happens to be there, in the browser.

+
+
+

Applications built in this style are not hypermedia driven: they do not take advantage of + the underlying hypermedia + system of the web.

+
+
+

To explain what a hypermedia driven application looks like, and to contrast it with the popular SPA + approach of today, + we need to first explore the entire hypermedia system of the web, beyond just discussing + HTML. We need to look at the + network architecture of the original web, including how a web server delivers a hypermedia + API, and how to effectively + use the hypermedia features available in the hypermedia client (e.g. the browser). +

+
+
+

Each of these are important aspects of building an effective hypermedia driven application, and it is + the entire + hypermedia system that comes together to make hypermedia such a powerful architecture. +

+
+

What is a Hypermedia System?

+
+

To understand what a hypermedia system is we’ll first take an in-depth look at the + canonical hypermedia system: the + World Wide Web. Roy Fielding, an engineer who helped create specifications and build the + implementations of many early pieces of the web, gave us the term REpresentational State Transfer, + or REST. + In his PhD dissertation he described REST as a network architecture, and he contrasted it + with earlier approaches to building + distributed software.

+
+
+

We define a hypermedia system as a system that adheres to the RESTful network architecture + in Fielding’s original + sense of this term.

+
+
+

Unfortunately, today, you probably associate the term "REST" with JSON APIs, since that is where the + term is typically + used in industry. This is a misapplied use of the term REST because JSON is not a natural + hypermedia due to the absense of + hypermedia controls. The exhange of hypermedia is an explicit requirement for a system to be + considered "RESTful." + It is a long story how we got here, using the term REST so incorrectly, and we will go into the + details later in this book. + But, for now, if you currently think "REST == JSON", please try to set that understanding aside + while reading this book, + and come to the concept with fresh eyes.

+
+
+

It is important to understand that, in his dissertation, Fielding was describing The World Wide Web + as it existed in the + late 1990s. The web, at that point, was simply web browsers exchanging hypermedia. That system, with + its simple links + and forms, was what Fielding was calling RESTful.

+
+
+

JSON APIs were a decade away from becoming a common tool in web development: REST was about + hypermedia and the Web 1.0.

+
+

Hypermedia-Driven Applications

+
+

In this book we are going to take a look at hypermedia as a system architecture and then + explore some practical, + modern approaches to building web applications using it. We will call applications built in + this style + Hypermedia-Driven Applications, or HDAs, and we contrast them with a popular style in use + today, the Single Page + Application. +

+
+
+

A Hypermedia-Driven Application is an application built on top of a hypermedia system that respects + and utilizes the + hypermedia functionality of that underlying system.

+
+

Goals

+
+

The goal of this book is to give you a strong sense of how the RESTful, hypermedia system + architecture differs from + other client-server systems, and what the strengths (and weaknesses) of the hypermedia approach are. + Further, we hope + to convince you that the hypermedia architecture is relevant to developers building modern + web applications.

+
+
+

We aim to give you the tools to evaluate the requirements for an application and answer the question: +

+
+
+

“Could I build this as a Hypermedia-Driven Application?”

+
+
+

We hope that for many applications the answer to that question will be “Yes!”

+
+

Book Layout

+
+

The book is broken into three parts:

+
+
+
    +
  • +

    An introduction (or re-introduction) to hypermedia, with a particular focus on HTML and HTTP. + We will finish this + review of core hypermedia concepts by creating a simple "Web 1.0"-style application, + Contact.app, for managing contacts.

    +
  • +
  • +

    Next we will look at how we can use htmx, a + hypermedia-oriented JavaScript library created by the + authors of this book, to improve Contact.app. By using htmx, we will be able to achieve a + level of interactivity in our + application that many developers would expect to require a large, sophisticated front end + library, such as React. + Thanks to htmx, we will be able to do this without abandoning hypermedia as a + system architecture.

    +
  • +
  • +

    Finally, we will look at a completely different hypermedia system, Hyperview. Hyperview is a + mobile hypermedia system, related to, but distinct from the web and created by one + of the authors of this book — Adam Stepinski. It supports mobile + specific features by providing not only a mobile specific hypermedia, but also a + mobile hypermedia client, a network protocol and so on. It provides a full mobile + hypermedia system for you to build your mobile application with, and, in doing so, + makes it possible to build mobile Hypermedia-Driven Applications.

    +
  • +
+
+
+

Note that each section is somewhat independent of the others. If you already know hypermedia + in-depth and how basic Web + 1.0 applications function, you may want to skip ahead to the second section on htmx and how to build + modern web applications + using hypermedia. Similarly, if you are well versed in htmx and want to dive into a novel + mobile hypermedia, + you can skip ahead to the Hyperview section.

+
+
+

That being said, the book is designed to be read in order and both the htmx and Hyperview sections + build on the Web 1.0 + application described at the end of the first section. Furthermore, even if you are well + versed in all the concepts + of hypermedia and details of HTML & HTTP, it is likely worth it to at least skim through the + first few chapters for + a refresher.

+
+

Hypermedia: A New Generation

+
+

Hypermedia isn’t a frequent topic of discussion these days. Even many older programmers who + grew up with the web + in the late 1990s and early 2000s haven’t thought much about these ideas in years. Many + younger web developers have + grown up knowing nothing but Single Page Applications and the frameworks that are used to build + them.

+
+
+

In particular, many young web developers began their careers by building React.js applications that + interact with a Node server using a + JSON API; they may never have learned about hypermedia as a system at all.

+
+
+

This is a tragedy, and, frankly, a failure on the part of the thought leaders in the web development + community to properly + communicate and advocate for the hypermedia approach.

+
+
+

Hypermedia was a great idea! It still is!

+
+
+

By the end of this book, you will have the tools and the language to put this great idea to + work in your own + applications. And, further, you will be able to bring the ideas and concepts of hypermedia systems + to the broader web development community.

+
+
+

Hypermedia can compete, hypermedia can win, hypermedia has won as an architectural + choice against the Single + Page Application approach, but only if smart people (like you) learn about it, build with + it and then tell the world + about it.

+
+
+
+
+

Remember the message? “The future is not set. There is no fate but what we make for + ourselves.”

+
+
+
+ — Kyle Reese
+ Terminator 2: Judgement Day +
+
+
+
+
+

1. Hypermedia: A Reintroduction

+
+
+

Hypermedia is a universal technology today, almost as common as electricity.

+
+
+

Billions of people use hypermedia-based systems every day, mainly by interacting with the + Hypertext Markup Language + (HTML) being exchanged via the Hypertext Transfer Protocol (HTTP) by using a web + browser connected to the World Wide Web.

+
+
+

People use these systems to get their news, check in on friends, buy things online, play games, send + emails and so + forth: the variety and sheer number of online services being delivered by hypermedia is truly + astonishing.

+
+
+

And yet, despite this ubiquity, the topic of hypermedia itself is a strangely under-explored concept + today, left mainly to + specialists. Yes, you can find a lot of tutorials on how to author HTML, create links and forms, + etc. But it is rare + to see a discussion of HTML as a hypermedia and, more broadly, on how an entire hypermedia + system fits together.

+
+
+

This is in contrast with the early web development era when concepts like Representational State + Transfer (REST) + and Hypermedia As The Engine of Application State (HATEOAS) were discussed frequently, + refined and debated among + web developers.

+
+
+

In a sad turn of events, today, the world’s most popular hypermedia, HTML, is often viewed + resentfully: it is an + awkward, legacy markup language that must be grudgingly used to build user interfaces in what are + increasingly entirely JavaScript-based web applications.

+
+
+

HTML happens to be there, in the browser, and so we have to use it.

+
+
+

This is a shame and we hope to convince you that hypermedia is not simply a + piece of legacy technology that we have to accept and deal with. Instead, we aim to show you that + hypermedia is a + tremendously innovative, simple and flexible way to build robust applications: + Hypermedia-Driven Applications.

+
+
+

We hope that by the end of this book you will feel, as we do, that the hypermedia approach deserves a + seat at the table + when you, a web developer, are considering the architecture of your next application. Creating a + Hypermedia-Driven + Application on top of a hypermedia system like the web is a viable and, indeed, often + excellent choice for + modern web applications. +

+
+
+

(And, as the section on Hyperview will show, not just web applications.)

+
+
+

1.1. What Is Hypermedia?

+
+
+
+

Hypertexts: new forms of writing, appearing on computer screens, that will branch or + perform at the reader’s + command. A hypertext is a non-sequential piece of writing; only the computer display + makes it practical.

+
+
+
+ — Ted Nelson
+ https://archive.org/details/SelectedPapers1977/page/n7/mode/2up +
+
+
+

Let us begin at the beginning: what is hypermedia?

+
+
+

Hypermedia is a media, for example a text, that includes non-linear branching from one + location in the media to another, + via, for example, hyperlinks embedded in the media. The prefix “hyper-” derives from + the Greek prefix “ὑπερ-” which + means “beyond” or “over”, indicating that hypermedia goes + beyond normal, passively consumed media like magazines and + newspapers.

+
+
+

Hyperlinks are a canonical example of what is called a hypermedia control:

+
+
+
+
Hypermedia Control
+
+

A hypermedia control is an element in a hypermedia that describes (or controls) + some sort of + interaction, often with a remote server, by encoding information about that interaction + directly and completely within + itself.

+
+
+
+
+

Hypermedia controls are what differentiate hypermedia from other sorts of media.

+
+
+

You may be more familiar with the term hypertext, from whose Wikipedia page the above + quote is taken. Hypertext + is a sub-category of hypermedia and much of this book is going to discuss how to build modern + applications using + hypertexts such as HTML, the Hypertext Markup Language, or HXML, a hypertext used by the + Hyperview mobile hypermedia + system.

+
+
+

Hypertexts like HTML function alongside other technologies crucial for making an entire + hypermedia system work: network + protocols like HTTP, other media types such as images, videos, hypermedia servers (i.e. servers + providing hypermedia APIs), + sophisticated hypermedia clients (e.g. web browsers), and so on.

+
+
+

Because of this, we prefer the broader term hypermedia systems when describing the + underlying architecture of + applications built using hypertext, to emphasize the system architecture over the particular + hypermedia being used.

+
+
+

It is the entire hypermedia system architecture that is underappreciated and ignored by + many modern web developers.

+
+
+
+

1.2. A Brief History of Hypermedia

+
+

Where did the idea of hypermedia come from?

+
+
+

While there were many precursors to the modern idea of hypertext and the more general hypermedia, + many people point + to the 1945 article As We May Think written by Vannevar Bush in The Atlantic as a + starting point for looking at what + has become modern hypermedia.

+
+
+

In this article Bush described a device called a Memex, which, using a complex mechanical system + of reels and microfilm, + along with an encoding system, would allow users to jump between related frames of content. The + Memex was never actually + implemented, but it was an inspiration for later work on the idea of hypermedia.

+
+
+

The terms “hypertext” and “hypermedia” were coined in 1963 by Ted Nelson, + who would go on to work on the Hypertext Editing + System at Brown University and who later created the File Retrieval and Editing + System (FRESS), a shockingly advanced + hypermedia system for its time. (This was perhaps the first digital system to have a notion of + “undo”.)

+
+
+

While Nelson was working on his ideas, Douglas Engelbart was busy at work at the Stanford + Research Institute, explicitly + attempting to make Vannevar Bush’s Memex a reality. In 1968, Englebart gave “The + Mother of All Demos” in San Francisco, + California.

+
+
+

Englebart demonstrated an unbelievable amount of technology:

+
+
+
    +
  • +

    Remote, collaborative text editing with his peers in Menlo Park.

    +
  • +
  • +

    Video and audio chat.

    +
  • +
  • +

    An integrated windowing system, with window resizing, etc.

    +
  • +
  • +

    A recognizable hypertext, whereby clicking on underlined text navigated to new content. +

    +
  • +
+
+
+

Despite receiving a standing ovation from a shocked audience after his talk, it was decades + before the technologies + Englebart demonstrated became mainstream.

+
+
+

Modern Implementation

+
+

In 1990, Tim Berners-Lee, working at CERN, published the first website. He had been working + on the idea of hypertext + for a decade and had finally, out of desperation at the fact it was so hard for researchers + to share their research, + found the right moment and institutional support to create the World Wide Web:

+
+
+
+
+

Creating the web was really an act of desperation, because the situation without it + was very difficult when I was working + at CERN later. Most of the technology involved in the web, like the hypertext, like + the Internet, multifont text objects, had all + been designed already. I just had to put them together. It was a step of + generalising, going to a higher level of abstraction, + thinking about all the documentation systems out there as being possibly part of a + larger imaginary documentation system.

+
+
+
+ — Tim Berners-Lee
+ https://britishheritage.org/tim-berners-lee-the-world-wide-web +
+
+
+

By 1994 his creation was taking off so quickly that Berners-Lee founded the W3C, a working + group of companies and researchers + tasked with improving the web. All standards created by the W3C were royalty-free and could + be adopted and implemented + by anyone, cementing the open, collaborative nature of the web.

+
+
+

In 2000, Roy Fielding, then at U.C. Irvine, published a seminal PhD dissertation on the web: + “Architectural Styles and the + Design of Network-based Software Architectures.” Fielding had been working on the open + source Apache HTTP Server and + his thesis was a description of what he felt was a new and distinct networking + architecture that had emerged in the early + web. Fielding had worked on the initial HTTP specifications and, in the paper, defined the + web’s hypermedia + network model using the term REpresentational State Transfer (REST).

+
+
+

Fielding’s work became a major touchstone for early web developers, giving them a + language to discuss the new technical + medium they were building applications in.

+
+
+

We will discuss Fielding’s key ideas in depth in Chapter 3, and try to correct the + record with respect to REST, + HATEOAS and hypermedia.

+
+
+
+
+

1.3. The World’s Most Successful Hypertext: + HTML

+
+
+
+

In the beginning was the hyperlink, and the hyperlink was with the web, and the hyperlink + was the web. And it was good.

+
+
+
+ — Rescuing REST From the API Winter
+ https://intercoolerjs.org/2016/01/18/rescuing-rest.html +
+
+
+

The system that Berners-Lee, Fielding and many others had created revolved around a hypermedia: + HTML. HTML started as a read-only + hypermedia, used to publish (at first) academic documents. These documents were linked together + via anchor tags which + created hyperlinks between them, allowing users to quickly navigate between documents. +

+
+
+

When HTML 2.0 was released, it introduced the notion of the form tag, joining the + anchor tag (i.e. hyperlink) as a + second hypermedia control. The introduction of the form tag made building applications + on the web viable by providing + a mechanism for updating resources, rather than just reading them.

+
+
+

It was at this point that the web transitioned from an interesting document-oriented system to a + compelling + application architecture. +

+
+
+

Today HTML is the most widely used hypermedia in existence and this book naturally assumes that + the reader has a + reasonable familiarity with it. You do not need to be an HTML (or CSS) expert to understand the + code in this book, but + the better you understand the core tags and concepts of HTML, the more you will get out of it. +

+
+
+

The Essence of HTML as a Hypermedia

+
+

Let us consider these two defining hypermedia elements (that is the two defining + hypermedia controls) of HTML, + the anchor tag and the form tag, in a bit of detail.

+
+
+
Anchor Tags
+
+

Anchor tags are so familiar as to be boring but, as the original hypermedia control, it + is worth reviewing the mechanics + of hyperlinks to get our minds in the right place for developing a deeper understanding + of hypermedia.

+
+
+

Consider a simple anchor tag, embedded within a larger HTML document:

+
+
+
A Simple Hyperlink
+
+
<a href="https://hypermedia.systems/">
   Hypermedia Systems
 </a>
-
-
-
-

An anchor tag consists of the tag itself, <a></a>, as well as the attributes and content within the tag. Of particular -interest is the href attribute, which specifies a hypertext reference to another document or document fragment. It -is this attribute that makes the anchor tag a hypermedia control.

-
-
-

In a typical web browser, this anchor tag would be interpreted to mean:

-
-
-
    -
  • -

    Show the text “Hypermedia Systems” in a manner indicating that it is clickable.

    -
  • -
  • -

    When the user clicks on that text, issue an HTTP GET request to the URL https://hypermedia.systems/.

    -
  • -
  • -

    Take the HTML content in the body of the HTTP response to this request and replace the entire screen in the browser as a new -document, updating the navigation bar to this new URL.

    -
  • -
-
-
-

Anchors provide the main mechanism we use to navigate around the web today, by selecting links to navigate from document -to document, or from resource to resource.

-
-
-

Here is what a user interaction with an anchor tag/hyperlink looks like in visual form:

-
-
-
An HTTP GET In Action
-
-
┌────────────────────────┐   ┌─HTTP REQUEST────────────────┐
+                            
+
+
+

An anchor tag consists of the tag itself, <a></a>, as well as + the attributes and content within the tag. Of particular + interest is the href attribute, which specifies a hypertext + reference to another document or document fragment. It + is this attribute that makes the anchor tag a hypermedia control.

+
+
+

In a typical web browser, this anchor tag would be interpreted to mean:

+
+
+
    +
  • +

    Show the text “Hypermedia Systems” in a manner indicating that it is + clickable.

    +
  • +
  • +

    When the user clicks on that text, issue an HTTP GET request to the + URL + https://hypermedia.systems/. +

    +
  • +
  • +

    Take the HTML content in the body of the HTTP response to this request and + replace the entire screen in the browser as a new + document, updating the navigation bar to this new URL.

    +
  • +
+
+
+

Anchors provide the main mechanism we use to navigate around the web today, by selecting + links to navigate from document + to document, or from resource to resource.

+
+
+

Here is what a user interaction with an anchor tag/hyperlink looks like in visual form: +

+
+
+
An HTTP GET In Action
+
+
┌────────────────────────┐   ┌─HTTP REQUEST────────────────┐
 │ BROWSER              X │   │                             │
 ├────────────────────────┤   │ GET /                       │
 │                        │   │ Host: hypermedia.systems    │
@@ -766,96 +3785,121 @@ to document, or from resource to resource.

└────────────────────────┘ │ <h1>Hypermedia Systems</h1> │ │ ... │ └─────────────────────────────┘
-
-
-
-

When the link is clicked the browser (or, as we sometimes refer to it, the hypermedia client) initiates an HTTP -GET request to the URL encoded in the link’s href attribute.

-
-
-

Note that the HTTP request includes additional data (i.e., metadata) on what, exactly, the browser wants from the server, -in the form of headers. We will discuss these headers, and HTTP in more depth in Chapter 3.

-
-
-

The hypermedia server then responds to this request with a hypermedia response — the HTML — for the new page. -This may seem like a small and obvious point, but it is an absolutely crucial aspect of a truly RESTful hypermedia -system: the client and server must communicate via hypermedia!

-
-
-
-
Form Tags
-
-

Anchor tags provide navigation between documents or resources, but don’t allow you to update those resources. That functionality -falls to the form tag.

-
-
-

Here is a simple example of a form in HTML:

-
-
-
A Simple Form
-
-
<form action="/signup" method="post">
+                            
+
+
+

When the link is clicked the browser (or, as we sometimes refer to it, the hypermedia + client) initiates an HTTP + GET request to the URL encoded in the link’s href + attribute. +

+
+
+

Note that the HTTP request includes additional data (i.e., metadata) on what, + exactly, the browser wants from the server, + in the form of headers. We will discuss these headers, and HTTP in more depth in Chapter + 3.

+
+
+

The hypermedia server then responds to this request with a hypermedia + response — the HTML — for the new page. + This may seem like a small and obvious point, but it is an absolutely crucial aspect of + a truly RESTful hypermedia + system: the client and server must communicate via hypermedia!

+
+
+
+
Form Tags
+
+

Anchor tags provide navigation between documents or resources, but don’t + allow you to update those resources. That functionality + falls to the form tag.

+
+
+

Here is a simple example of a form in HTML:

+
+
+
A Simple Form
+
+
<form action="/signup" method="post">
   <input type="text" name="email" placeholder="Enter Email To Sign Up..."/>
   <button>Sign Up</button>
 </form>
-
-
-
-

Like an anchor tag, a form tag consists of the tag itself, <form></form>, combined with the attributes and -content within the tag. Note that the form tag does not have an href attribute, but rather has an action attribute -that specifies where to issue an HTTP request.

-
-
-

Furthermore, it also has a method attribute, which specifies exactly which HTTP “method” to use. In this example -the form is asking the browser to issue a POST request.

-
-
-

In contrast with anchor tags, the content and tags within a form can have an effect on the hypermedia interaction -that the form makes with a server. The values of input tags and other tags such as select tags will be included -with the HTTP request when the form is submitted, as URL parameters in the case of a GET and as part of the request -body in the case of a POST. This allows a form to include an arbitrary amount of information -collected from a user in a request, unlike the anchor tag.

-
-
-

In a typical browser this form tag and its contents would be interpreted by the browser roughly as follows:

-
-
-
    -
  • -

    Show a text input and a “Sign Up” button to the user.

    -
  • -
  • -

    When the user submits the form by clicking the “Sign Up” button or by hitting the enter key while the input element is -focused, issue an HTTP POST request to the path /signup on the “current” server.

    -
  • -
  • -

    Take the HTML content in the body of the HTTP response body and replace the entire screen in the browser as a new -document, updating the navigation bar to this new URL.

    -
  • -
-
-
-

This mechanism allows the user to issue requests to update the state of resources on the server. Note that despite -this new type of request the communication between client and server is still done entirely with hypermedia.

-
-
-

It is the form tag that makes Hypermedia-Driven Applications possible.

-
-
-

If you are an experienced web developer you probably recognize that we are omitting a few details and complications -here. For example, the response to a form submission often redirects the client to a different URL.

-
-
-

This is true, and we will get down into the muck with forms in more detail in later chapters but, for now, this simple -example suffices to demonstrate the core mechanism for updating system state purely within hypermedia.

-
-
-

Here is a diagram of the interaction:

-
-
-
An HTTP POST In Action
-
-
┌────────────────────────┐   ┌─HTTP REQUEST────────────────┐
+                            
+
+
+

Like an anchor tag, a form tag consists of the tag itself, + <form></form>, combined with the attributes and + content within the tag. Note that the form tag does not have an href + attribute, but rather has an action attribute + that specifies where to issue an HTTP request.

+
+
+

Furthermore, it also has a method attribute, which specifies exactly which + HTTP “method” to use. In this example + the form is asking the browser to issue a POST request.

+
+
+

In contrast with anchor tags, the content and tags within a form can have an + effect on the hypermedia interaction + that the form makes with a server. The values of input tags and + other tags such as select tags will be included + with the HTTP request when the form is submitted, as URL parameters in the case of a + GET and as part of the request + body in the case of a POST. This allows a form to include an arbitrary + amount of information + collected from a user in a request, unlike the anchor tag.

+
+
+

In a typical browser this form tag and its contents would be interpreted by the browser + roughly as follows:

+
+
+
    +
  • +

    Show a text input and a “Sign Up” button to the user.

    +
  • +
  • +

    When the user submits the form by clicking the “Sign Up” button or by + hitting the enter key while the input element is + focused, issue an HTTP POST request to the path + /signup on the “current” server.

    +
  • +
  • +

    Take the HTML content in the body of the HTTP response body and replace the + entire screen in the browser as a new + document, updating the navigation bar to this new URL.

    +
  • +
+
+
+

This mechanism allows the user to issue requests to update the state of + resources on the server. Note that despite + this new type of request the communication between client and server is still done + entirely with hypermedia.

+
+
+

It is the form tag that makes Hypermedia-Driven Applications possible.

+
+
+

If you are an experienced web developer you probably recognize that we are omitting a few + details and complications + here. For example, the response to a form submission often redirects the client + to a different URL.

+
+
+

This is true, and we will get down into the muck with forms in more detail in later + chapters but, for now, this simple + example suffices to demonstrate the core mechanism for updating system state purely + within hypermedia.

+
+
+

Here is a diagram of the interaction:

+
+
+
An HTTP POST In Action
+
+
┌────────────────────────┐   ┌─HTTP REQUEST────────────────┐
 │ BROWSER              X │   │                             │
 ├────────────────────────┤   │ POST /sign-up               │
 │                        │   │ Host: hypermedia.systems    │
@@ -884,805 +3928,1040 @@ example suffices to demonstrate the core mechanism for updating system state pur
 └────────────────────────┘   │ <h1>Thank you for signing   │
                              │ up</h1>                     │
                              └─────────────────────────────┘
-
-
-
-
-
Web 1.0 Applications
-
-

As someone interested in web development, the above diagrams and discussion are probably very familiar to you. You may -even find this content boring. But take a step back and consider the fact that these two hypermedia controls, -anchors and forms, are the only native ways for a user to interact with a server in plain HTML.

-
-
-

Only two tags!

-
-
-

And yet, armed with only these two tags, the early web was able to grow exponentially and offer a staggeringly large -amount of online, dynamic functionality to billions of people.

-
-
-

This is strong evidence of the power of hypermedia. Even today, in a web development world increasingly dominated by large -JavaScript-centric front end frameworks, many people choose to use simple vanilla HTML to achieve their application goals -and are often perfectly happy with the results.

-
-
-

These two tags give a tremendous amount of expressive power to HTML.

-
-
-
-
-

So What Isn’t Hypermedia?

-
-

So links and forms are the two main hypermedia-based mechanisms for interacting with a server available in HTML.

-
-
-

Now let’s consider a different approach: let’s interact with a server by issuing an HTTP request via JavaScript. To -do this, we will use the fetch() API, a popular API for -issuing an “Asynchronous JavaScript and XML,” or AJAX request, available in all modern web browsers:

-
-
-
JavaScript
-
-
<button onclick="fetch('/api/v1/contacts/1') (1)
+                            
+
+
+
+
Web 1.0 Applications
+
+

As someone interested in web development, the above diagrams and discussion are probably + very familiar to you. You may + even find this content boring. But take a step back and consider the fact that these two + hypermedia controls, + anchors and forms, are the only native ways for a user to interact with a + server in plain HTML.

+
+
+

Only two tags!

+
+
+

And yet, armed with only these two tags, the early web was able to grow exponentially and + offer a staggeringly large + amount of online, dynamic functionality to billions of people.

+
+
+

This is strong evidence of the power of hypermedia. Even today, in a web development + world increasingly dominated by large + JavaScript-centric front end frameworks, many people choose to use simple vanilla HTML + to achieve their application goals + and are often perfectly happy with the results.

+
+
+

These two tags give a tremendous amount of expressive power to HTML.

+
+
+
+
+

So What Isn’t Hypermedia?

+
+

So links and forms are the two main hypermedia-based mechanisms for interacting with a server + available in HTML.

+
+
+

Now let’s consider a different approach: let’s interact with a server by issuing + an HTTP request via JavaScript. To + do this, we will use the fetch() + API, a popular API for + issuing an “Asynchronous JavaScript and XML,” or AJAX request, available in all + modern web browsers:

+
+
+
JavaScript
+
+
<button onclick="fetch('/api/v1/contacts/1') (1)
                  .then(response => response.json()) (2)
                  .then(data => updateUI(data))"> (3)
     Fetch Contacts
 </button>
-
-
-
-
    -
  1. -

    Issue the request.

    -
  2. -
  3. -

    Convert the response to a JavaScript object.

    -
  4. -
  5. -

    Invoke the updateUI() function with the object.

    -
  6. -
-
-
-

This button has an onclick attribute that specifies some JavaScript to run when the button is clicked.

-
-
-

The JavaScript will issue an AJAX HTTP GET request to /api/v1/contacts/1 using fetch(). An AJAX request is like a -“normal” HTTP request, but it is issued “behind the scenes” by the browser. The user does not see a -request indicator from the browser as they would with normal links and forms. Additionally, unlike requests issued by those -hypermedia controls, it is up to the JavaScript code to handle the response from the server.

-
-
-

Despite AJAX having XML as part of its acronym, today the HTTP response to this request would almost certainly be in the -JavaScript Object Notation (JSON) format rather than XML.

-
-
-

An HTTP response to this request might look something like this:

-
-
-
JSON
-
-
{ (1)
+                        
+
+
+
    +
  1. +

    Issue the request.

    +
  2. +
  3. +

    Convert the response to a JavaScript object.

    +
  4. +
  5. +

    Invoke the updateUI() function with the object.

    +
  6. +
+
+
+

This button has an onclick attribute that specifies some JavaScript to run when + the button is clicked.

+
+
+

The JavaScript will issue an AJAX HTTP GET request to + /api/v1/contacts/1 using fetch(). An AJAX request is like a + “normal” HTTP request, but it is issued “behind the scenes” by the + browser. The user does not see a + request indicator from the browser as they would with normal links and forms. Additionally, + unlike requests issued by those + hypermedia controls, it is up to the JavaScript code to handle the response from the server. +

+
+
+

Despite AJAX having XML as part of its acronym, today the HTTP response to this request would + almost certainly be in the + JavaScript Object Notation (JSON) format rather than XML.

+
+
+

An HTTP response to this request might look something like this:

+
+
+
JSON
+
+
{ (1)
   "id": 42, (2)
   "email" : "json-example@example.org" (3)
 }
-
-
-
-
    -
  1. -

    The start of a JSON object.

    -
  2. -
  3. -

    A property, in this case with the name id and the value 42.

    -
  4. -
  5. -

    Another property, the email of the contact with this id.

    -
  6. -
-
-
-

The JavaScript code above converts the JSON text received from the server into a JavaScript object by calling the -json() method on it. This new JavaScript object object is then handed off to the updateUI() method.

-
-
-

The updateUI() method is responsible for updating the UI based on the data encoded in the JavaScript Object, -perhaps by displaying the contact in a bit of HTML generated via a client-side template in the JavaScript application.

-
-
-

The details of exactly what the updateUI() function does aren’t important for our discussion.

-
-
-

What is important, what is the crucial aspect of this JSON-based server interaction is that it is not using -hypermedia. The JSON API being used here does not return a hypermedia response. There are no hyperlinks or other -hypermedia-style controls in it.

-
-
-

This JSON API is, rather, a Data API.

-
-
-

Because the response is in JSON and is not hypermedia, the JavaScript updateUI() method must understand how to turn -this contact data into HTML.

-
-
-

In particular, the code in updateUI() needs to know about the internal structure and meaning of the data.

-
-
-

It needs to know:

-
-
-
    -
  • -

    Exactly how the fields in the JSON data object are structured and named.

    -
  • -
  • -

    How they relate to one another.

    -
  • -
  • -

    How to update the local data this new data corresponds with.

    -
  • -
  • -

    How to render this data to the browser.

    -
  • -
  • -

    What additional actions/API end points can be called with this data.

    -
  • -
-
-
-

In short, the logic in updateUI() needs to have intimate knowledge of the API endpoint at /api/v1/contact/1, knowledge provided -via some side-channel beyond the response itself. As a result, the updateUI() code and the -API have a strong relationship, known as tight coupling: if the format of the JSON response changes, then the code for updateUI() will almost certainly -also need to be changed.

-
-
-
Single Page Applications
-
-

This bit of JavaScript, while very modest, is the organic beginnings of a much larger conceptual approach to building -web applications. This is the beginning of a Single Page Application (SPA). The web application is no longer -navigating between pages using hypermedia controls as was the case with links and forms.

-
-
-

Instead, the application is exchanging plain data with the server and then updating the content within a single page.

-
-
-

When this strategy or architecture is adopted for an entire application, everything happens on a “Single Page” and, -thus the application becomes a “Single Page Application.”

-
-
-

The Single Page Application architecture is extremely popular today and has been the dominant approach to building web applications for the last decade. This can be observed by the high level of mind-share and discussion it has received in the industry.

-
-
-

Today the vast majority of Single Page Applications adopt far more sophisticated frameworks for managing their -user interface than this simple example shows. Popular libraries such as React, Angular, Vue.js, etc. are now the common — indeed, the standard — way to build web applications.

-
-
-

With these more complex frameworks developers typically work with an elaborate client-side model — that is, with JavaScript objects -stored locally in the browser’s memory that represent the “model” or “domain” of your application. These JavaScript objects -are updated via JavaScript code and the framework then “reacts” to these changes, updating the user interface.

-
-
-

When the user interface is updated by a user these changes also flow into the model objects, establishing a “two-way” -binding mechanism: the model can update the UI, and the UI can update the model.

-
-
-

This is all very sophisticated and, today, very popular. But the fact is that developers that adopt this approach to building -web applications have largely abandoned the web’s underlying hypermedia system.

-
-
-

HTML is still used to build user interfaces, but the hypermedia aspect of the two major hypermedia controls, -anchors and forms, are ignored. Neither tag interacts with a server via their native hypermedia mechanism. Rather, -they become mere user interface elements that drive local interactions with the in-memory domain model via JavaScript, -which is then synchronized with the server using plain data JSON APIs.

-
-
-

So, as with our simple button above, the Single Page Application approach is not built on top of a hypermedia architecture. -It does not take advantage of the existing RESTful architecture of the web, nor does it utilize the built-in functionality -found in HTML’s native hypermedia controls.

-
-
-

SPAs are somewhat like thick client applications like the client-server applications of the -1980s — an architecture popular before the web came along.

-
-
-

This approach isn’t necessarily wrong, but it is worth thinking about why web developers so frequently take it and -if there are reasons not to go down this path.

-
-
-
-
-
-

1.4. Why Use Hypermedia?

-
-
-
-

The emerging norm for web development is to build a React single-page application, with server rendering. The two key -elements of this architecture are something like:

-
-
-
    -
  1. -

    The main UI is built & updated in JavaScript using React or something similar.

    -
  2. -
  3. -

    The backend is an API that that application makes requests against.

    -
  4. -
-
-
-

This idea has really swept the internet. It started with a few major popular websites and has crept into corners like -marketing sites and blogs.

-
-
-
-— Tom MacWright
-https://macwright.com/2020/05/10/spa-fatigue.html -
-
-
-

The JavaScript-based Single Page Application approach has taken the web development world by storm, and there was one -major and very good reason for its success: The Single Page Application offers a far more interactive and immersive experience -than the old, gronky, Web 1.0 hypermedia-based applications could. It had the ability to smoothly update elements inline on -a page without a dramatic reload of the entire document, the ability to use CSS transitions to create nice visual effects, -the ability to hook into arbitrary events like mouse movements. All of these gave JavaScript-based applications a huge advantage -in building sophisticated user experiences.

-
-
-

So why on earth would you abandon this popular and modern approach for an older and much less discussed -approach such as hypermedia?

-
-
-

JavaScript Fatigue

-
-

Well, we are glad you asked.

-
-
-

It turns out that the hypermedia architecture, even in its original Web 1.0 form, has a number of advantages when compared with -the Single Page Application + JSON Data API approach:

-
-
-
    -
  • -

    It is an extremely simple approach to building web applications.

    -
  • -
  • -

    It is extremely tolerant of content and API changes. In fact, it thrives on them!

    -
  • -
  • -

    It leverages tried and true features of web browsers, such as caching.

    -
  • -
-
-
-

The first two advantages, in particular, address major pain points in modern web development:

-
-
-
    -
  • -

    Single Page Application infrastructure has become extremely complex, often requiring an entire team to manage.

    -
  • -
  • -

    JSON API churn — constant changes made to JSON APIs to support application needs — has become a major pain point for -many application teams.

    -
  • -
-
-
-

The combination of these two problems, along with other issues such as JavaScript library churn, has led to a phenomenon known as “JavaScript Fatigue.” This refers to a general sense of exhaustion with all the hoops that are necessary to jump through to -get anything done in modern-day web applications.

-
-
-

We believe that a hypermedia architecture can help cure JavaScript Fatigue for many developers and teams. But if hypermedia is so great, and if it addresses so many of the problems that beset the web -development industry, why was it abandoned in the first place? After all, hypermedia was there first.

-
-
-

Why didn’t web developers just stick with it?

-
-
-

We believe that hypermedia hasn’t made a comeback yet for two reasons.

-
-
-

The first is this: the expressiveness of HTML as a hypermedia hasn’t changed much, if at all, since HTML 2.0, which -was released in the mid 1990s. Many new features have been added to HTML, of course, but there haven’t been any -major new ways to interact with a server in HTML in almost three decades.

-
-
-

HTML developers still only have anchor tags and forms available as hypermedia controls, and those hypermedia controls -can still only issue GET and POST requests.

-
-
-

This baffling lack of progress by HTML leads immediately to the second, and perhaps more practical reason that -HTML-as-hypermedia has fallen on hard times: as the interactivity and expressiveness of HTML has remained frozen, the -demands of web users have continued to increase, calling for more and more interactive web applications.

-
-
-

JavaScript-based applications coupled to data-oriented JSON APIs have stepped in as a way to provide these more -sophisticated user interfaces. It was the user experience that you could achieve in JavaScript, and that you couldn’t achieve in plain HTML, that drove the web development community to the JavaScript-based -Single Page Application approach. The shift was not driven by any inherent superiority of the Single Page Application as a system -architecture.

-
-
-

It didn’t have to be this way. There is nothing intrinsic to the idea of hypermedia that prevents it from having a -richer, more expressive interactivity model than vanilla HTML. Rather than abandoning -the hypermedia architecture, the industry could have demanded more interactivity from HTML.

-
-
-

But the industry didn’t. Instead, it reverted to making thick-client style applications within web browsers, in an -understandable move to a more familiar model for building rich applications.

-
-
-

Not everyone abandoned hypermedia, of course. There have been heroic efforts to continue to advance hypermedia outside of -HTML, efforts like HyTime, VoiceXML, and HAL.

-
-
-

But HTML, the most widely used hypermedia in the world, stopped making progress as a hypermedia. The web development -world moved on, solving the interactivity problems with HTML and adopting a completely different -system architecture along the way.

-
-
-
-
-

1.5. A Hypermedia Resurgence?

-
-

It is interesting to think about how HTML could have advanced. Instead of stalling as a hypermedia, how could HTML -have continued to develop? Could it have kept adding new hypermedia controls and increasing the expressiveness of -existing ones? Would it have been possible to build modern web applications within this original, hypermedia-oriented -and RESTful model that made the early web so powerful, so flexible, so…​ fun?

-
-
-

This might seem like idle speculation, but we have some good news on this score: in the last decade a few -idiosyncratic, alternative front end libraries have arisen that attempt to get HTML moving again. Ironically, these -libraries are written in JavaScript, the technology that supplanted HTML as the center of web development.

-
-
-

However, these libraries use JavaScript not as a replacement for the fundamental hypermedia system of the web.

-
-
-

Instead, they use JavaScript to augment HTML itself as a hypermedia.

-
-
-

These hypermedia-oriented libraries re-center hypermedia as the core technology in web applications.

-
-
-

Hypermedia-Oriented JavaScript Libraries

-
-

In the web development world there is an ongoing debate between the Single Page Application (SPA) approach and what is now being called the -“Multi-Page Application” (MPA) approach. MPA is a modern name for the old, Web 1.0 way of building web applications, using -links and forms located on multiple web pages, submitting HTTP requests and getting HTML responses.

-
-
-

MPA applications, by their nature, are Hypermedia-Driven Applications: after all, they are exactly what Roy Fielding -was describing in his dissertation.

-
-
-

These applications tend to be clunky, but they work reasonably well. Many web developers and teams choose to accept the limitations of plain HTML in the interest of simplicity and reliability.

-
-
-

Rich Harris, creator of Svelte.js, a popular SPA library, and a thought-leader on the SPA side of the debate, has proposed a mix -of this older MPA style and the newer SPA style. Harris calls this approach to building web applications “transitional,” in that -it attempts to blend the MPA approach and the newer SPA approach into a coherent whole. (This is somewhat -similar to the “transitional” trend in architecture, which combines traditional and modern architectural styles.)

-
-
-

“Transitional” is a fitting term for mixed-style applications, and it offers a reasonable compromise between the two approaches, using either one as appropriate on a case-by-case basis.

-
-
-

But this compromise still feels unsatisfactory.

-
-
-

Must we default to having these two very different architectural models in our applications?

-
-
-

Recall that the crux of the trade-off between SPAs and MPAs is the user experience, or interactivity of the application. -This typically drives the decision to choose one approach versus the other for an application or — in the case -of a “transitional” application — for a particular feature.

-
-
-

It turns out that by adopting a hypermedia-oriented library, the interactivity gap between the MPA and the SPA approach -closes dramatically. You can use the MPA approach, that is, the hypermedia approach, for much more of your application -without compromising your user interface. You might even be able to use the hypermedia approach for all your application -needs.

-
-
-

Rather than having an SPA with a bit of hypermedia around the edges, or some mix of the two approaches, you can often create -a web application that is primarily or entirely hypermedia driven, and that still satisfies the interactivity that your -users require.

-
-
-

This can tremendously simplify your web application and produce a much more coherent and understandable piece of -software. While there are still times and places for the more complex SPA approach, which we will discuss later in the book, -by adopting a hypermedia-first approach and using a hypermedia-oriented library to push HTML as far as possible, -your web application can be powerful, interactive and simple.

-
-
-

One such hypermedia oriented library is htmx. Htmx will be the focus of Part Two. -We show that you can, in fact, create many common “modern” UI features found in sophisticated Single -Page Applications by instead using the hypermedia model.

-
-
-

And, it is refreshingly fun and simple to do so.

-
-
-
-

Hypermedia-Driven Applications

-
-

When building a web application with htmx the term Multi-Page Application applies roughly, but it doesn’t fully characterize -the core of the application architecture. As you will see, htmx doesn’t need to replace entire pages, and, in fact, an -htmx-based application can reside entirely within a single page. We don’t recommend this practice, but it is -possible!

-
-
-

So it isn’t quite right to call web applications built with htmx "Multi-Page Applications." What the older Web 1.0 MPA -approach and the newer hypermedia-oriented library powered applications have in common is their use of hypermedia as -their core technology and architecture.

-
-
-

Therefore, we use the term Hypermedia-Driven Applications (HDAs) to describe both.

-
-
-

This clarifies that the core distinction between these two approaches and the SPA approach isn’t the number of pages -in the application, but rather the underlying system architecture.

-
-
-
-
Hypermedia-Driven Application (HDA)
-
-

A web application that uses hypermedia and hypermedia exchanges as its primary -mechanism for communicating with a server.

-
-
-
-
-

So, what does an HDA look like up close?

-
-
-

Let’s look at an htmx-powered implementation of the simple JavaScript-powered button above:

-
-
-
An Htmx Implementation
-
-
<button hx-get="/contacts/1" hx-target="#contact-ui"> (1)
+                        
+
+
+
    +
  1. +

    The start of a JSON object.

    +
  2. +
  3. +

    A property, in this case with the name id and the value 42. +

    +
  4. +
  5. +

    Another property, the email of the contact with this id.

    +
  6. +
+
+
+

The JavaScript code above converts the JSON text received from the server into a JavaScript + object by calling the + json() method on it. This new JavaScript object object is then handed off to + the updateUI() method. +

+
+
+

The updateUI() method is responsible for updating the UI based on the data + encoded in the JavaScript Object, + perhaps by displaying the contact in a bit of HTML generated via a client-side template in + the JavaScript application.

+
+
+

The details of exactly what the updateUI() function does aren’t important + for our discussion.

+
+
+

What is important, what is the crucial aspect of this JSON-based server + interaction is that it is not using + hypermedia. The JSON API being used here does not return a hypermedia response. There are no + hyperlinks or other + hypermedia-style controls in it.

+
+
+

This JSON API is, rather, a Data API.

+
+
+

Because the response is in JSON and is not hypermedia, the JavaScript + updateUI() method must understand how to turn + this contact data into HTML.

+
+
+

In particular, the code in updateUI() needs to know about the internal + structure and meaning of the data.

+
+
+

It needs to know:

+
+
+
    +
  • +

    Exactly how the fields in the JSON data object are structured and named.

    +
  • +
  • +

    How they relate to one another.

    +
  • +
  • +

    How to update the local data this new data corresponds with.

    +
  • +
  • +

    How to render this data to the browser.

    +
  • +
  • +

    What additional actions/API end points can be called with this data.

    +
  • +
+
+
+

In short, the logic in updateUI() needs to have intimate knowledge of the API + endpoint at /api/v1/contact/1, knowledge provided + via some side-channel beyond the response itself. As a result, the updateUI() + code and the + API have a strong relationship, known as tight coupling: if the format of the JSON + response changes, then the code for updateUI() will almost certainly + also need to be changed.

+
+
+
Single Page Applications
+
+

This bit of JavaScript, while very modest, is the organic beginnings of a much larger + conceptual approach to building + web applications. This is the beginning of a Single Page Application (SPA). The + web application is no longer + navigating between pages using hypermedia controls as was the case with links + and forms.

+
+
+

Instead, the application is exchanging plain data with the server and then + updating the content within a single page.

+
+
+

When this strategy or architecture is adopted for an entire application, everything + happens on a “Single Page” and, + thus the application becomes a “Single Page Application.”

+
+
+

The Single Page Application architecture is extremely popular today and has been the + dominant approach to building web applications for the last decade. This can be observed + by the high level of mind-share and discussion it has received in the industry.

+
+
+

Today the vast majority of Single Page Applications adopt far more sophisticated + frameworks for managing their + user interface than this simple example shows. Popular libraries such as React, Angular, + Vue.js, etc. are now the common — indeed, the + standard — way to build web applications.

+
+
+

With these more complex frameworks developers typically work with an elaborate + client-side model — that is, with JavaScript objects + stored locally in the browser’s memory that represent the “model” or + “domain” of your application. These JavaScript objects + are updated via JavaScript code and the framework then “reacts” to these + changes, updating the user interface.

+
+
+

When the user interface is updated by a user these changes also flow into the + model objects, establishing a “two-way” + binding mechanism: the model can update the UI, and the UI can update the model.

+
+
+

This is all very sophisticated and, today, very popular. But the fact is that developers + that adopt this approach to building + web applications have largely abandoned the web’s underlying hypermedia system. +

+
+
+

HTML is still used to build user interfaces, but the hypermedia aspect of the + two major hypermedia controls, + anchors and forms, are ignored. Neither tag interacts with a server via their native + hypermedia mechanism. Rather, + they become mere user interface elements that drive local interactions with the + in-memory domain model via JavaScript, + which is then synchronized with the server using plain data JSON APIs.

+
+
+

So, as with our simple button above, the Single Page Application approach is not + built on top of a hypermedia architecture. + It does not take advantage of the existing RESTful architecture of the web, nor does it + utilize the built-in functionality + found in HTML’s native hypermedia controls.

+
+
+

SPAs are somewhat like thick client applications like the client-server + applications of the + 1980s — an architecture popular before the web came along. +

+
+
+

This approach isn’t necessarily wrong, but it is worth thinking about + why web developers so frequently take it and + if there are reasons not to go down this path.

+
+
+
+
+
+

1.4. Why Use Hypermedia?

+
+
+
+

The emerging norm for web development is to build a React single-page application, with + server rendering. The two key + elements of this architecture are something like:

+
+
+
    +
  1. +

    The main UI is built & updated in JavaScript using React or something + similar.

    +
  2. +
  3. +

    The backend is an API that that application makes requests against.

    +
  4. +
+
+
+

This idea has really swept the internet. It started with a few major popular websites and + has crept into corners like + marketing sites and blogs.

+
+
+
+ — Tom MacWright
+ https://macwright.com/2020/05/10/spa-fatigue.html +
+
+
+

The JavaScript-based Single Page Application approach has taken the web development world by + storm, and there was one + major and very good reason for its success: The Single Page Application offers a far more + interactive and immersive experience + than the old, gronky, Web 1.0 hypermedia-based applications could. It had the ability to + smoothly update elements inline on + a page without a dramatic reload of the entire document, the ability to use CSS transitions to + create nice visual effects, + the ability to hook into arbitrary events like mouse movements. All of these gave + JavaScript-based applications a huge advantage + in building sophisticated user experiences.

+
+
+

So why on earth would you abandon this popular and modern approach for an older and much less + discussed + approach such as hypermedia?

+
+
+

JavaScript Fatigue

+
+

Well, we are glad you asked.

+
+
+

It turns out that the hypermedia architecture, even in its original Web 1.0 form, has a + number of advantages when compared with + the Single Page Application + JSON Data API approach:

+
+
+
    +
  • +

    It is an extremely simple approach to building web applications.

    +
  • +
  • +

    It is extremely tolerant of content and API changes. In fact, it thrives on them!

    +
  • +
  • +

    It leverages tried and true features of web browsers, such as caching.

    +
  • +
+
+
+

The first two advantages, in particular, address major pain points in modern web development: +

+
+
+
    +
  • +

    Single Page Application infrastructure has become extremely complex, often requiring + an entire team to manage.

    +
  • +
  • +

    JSON API churn — constant changes made to JSON APIs to support + application needs — has become a major pain point for + many application teams.

    +
  • +
+
+
+

The combination of these two problems, along with other issues such as JavaScript library + churn, has led to a phenomenon known as “JavaScript Fatigue.” This refers to a + general sense of exhaustion with all the hoops that are necessary to jump through to + get anything done in modern-day web applications.

+
+
+

We believe that a hypermedia architecture can help cure JavaScript Fatigue for many + developers and teams. But if hypermedia is so great, and if it addresses so many of the + problems that beset the web + development industry, why was it abandoned in the first place? After all, hypermedia was + there first.

+
+
+

Why didn’t web developers just stick with it?

+
+
+

We believe that hypermedia hasn’t made a comeback yet for two reasons.

+
+
+

The first is this: the expressiveness of HTML as a hypermedia hasn’t changed + much, if at all, since HTML 2.0, which + was released in the mid 1990s. Many new features have been added to HTML, + of course, but there haven’t been any + major new ways to interact with a server in HTML in almost three decades.

+
+
+

HTML developers still only have anchor tags and forms available as hypermedia controls, and + those hypermedia controls + can still only issue GET and POST requests.

+
+
+

This baffling lack of progress by HTML leads immediately to the second, and perhaps more + practical reason that + HTML-as-hypermedia has fallen on hard times: as the interactivity and expressiveness of HTML + has remained frozen, the + demands of web users have continued to increase, calling for more and more interactive web + applications.

+
+
+

JavaScript-based applications coupled to data-oriented JSON APIs have stepped in as a way to + provide these more + sophisticated user interfaces. It was the user experience that you could achieve in + JavaScript, and that you couldn’t achieve in plain HTML, that drove the web + development community to the JavaScript-based + Single Page Application approach. The shift was not driven by any inherent superiority of + the Single Page Application as a system + architecture.

+
+
+

It didn’t have to be this way. There is nothing intrinsic to the idea of + hypermedia that prevents it from having a + richer, more expressive interactivity model than vanilla HTML. Rather than abandoning + the hypermedia architecture, the industry could have demanded more interactivity from HTML. +

+
+
+

But the industry didn’t. Instead, it reverted to making thick-client style applications + within web browsers, in an + understandable move to a more familiar model for building rich applications.

+
+
+

Not everyone abandoned hypermedia, of course. There have been heroic efforts to continue to + advance hypermedia outside of + HTML, efforts like HyTime, VoiceXML, and HAL.

+
+
+

But HTML, the most widely used hypermedia in the world, stopped making progress as a + hypermedia. The web development + world moved on, solving the interactivity problems with HTML and adopting a completely + different + system architecture along the way.

+
+
+
+
+

1.5. A Hypermedia Resurgence?

+
+

It is interesting to think about how HTML could have advanced. Instead of stalling as a + hypermedia, how could HTML + have continued to develop? Could it have kept adding new hypermedia controls and increasing the + expressiveness of + existing ones? Would it have been possible to build modern web applications within this + original, hypermedia-oriented + and RESTful model that made the early web so powerful, so flexible, so…​ fun?

+
+
+

This might seem like idle speculation, but we have some good news on this score: in the last + decade a few + idiosyncratic, alternative front end libraries have arisen that attempt to get HTML moving + again. Ironically, these + libraries are written in JavaScript, the technology that supplanted HTML as the center of web + development.

+
+
+

However, these libraries use JavaScript not as a replacement for the fundamental + hypermedia system of the web.

+
+
+

Instead, they use JavaScript to augment HTML itself as a hypermedia.

+
+
+

These hypermedia-oriented libraries re-center hypermedia as the core technology in web + applications.

+
+
+

Hypermedia-Oriented JavaScript Libraries

+
+

In the web development world there is an ongoing debate between the Single Page Application + (SPA) approach and what is now being called the + “Multi-Page Application” (MPA) approach. MPA is a modern name for the old, Web + 1.0 way of building web applications, using + links and forms located on multiple web pages, submitting HTTP requests and getting HTML + responses.

+
+
+

MPA applications, by their nature, are Hypermedia-Driven Applications: after all, they are + exactly what Roy Fielding + was describing in his dissertation.

+
+
+

These applications tend to be clunky, but they work reasonably well. Many web developers and + teams choose to accept the limitations of plain HTML in the interest of simplicity and + reliability.

+
+
+

Rich Harris, creator of Svelte.js, a popular SPA library, and a thought-leader on the SPA + side of the debate, has proposed a mix + of this older MPA style and the newer SPA style. Harris calls this approach to building web + applications “transitional,” in that + it attempts to blend the MPA approach and the newer SPA approach into a coherent whole. + (This is somewhat + similar to the “transitional” trend in architecture, which combines traditional + and modern architectural styles.)

+
+
+

“Transitional” is a fitting term for mixed-style applications, and it offers a + reasonable compromise between the two approaches, using either one as appropriate on a + case-by-case basis.

+
+
+

But this compromise still feels unsatisfactory.

+
+
+

Must we default to having these two very different architectural models in our applications? +

+
+
+

Recall that the crux of the trade-off between SPAs and MPAs is the user experience, + or interactivity of the application. + This typically drives the decision to choose one approach versus the other for an + application or — in the case + of a “transitional” application — for a particular feature. +

+
+
+

It turns out that by adopting a hypermedia-oriented library, the interactivity gap between + the MPA and the SPA approach + closes dramatically. You can use the MPA approach, that is, the hypermedia approach, for + much more of your application + without compromising your user interface. You might even be able to use the hypermedia + approach for all your application + needs.

+
+
+

Rather than having an SPA with a bit of hypermedia around the edges, or some mix of the two + approaches, you can often create + a web application that is primarily or entirely hypermedia driven, and + that still satisfies the interactivity that your + users require.

+
+
+

This can tremendously simplify your web application and produce a much more coherent + and understandable piece of + software. While there are still times and places for the more complex SPA approach, which we + will discuss later in the book, + by adopting a hypermedia-first approach and using a hypermedia-oriented library to push HTML + as far as possible, + your web application can be powerful, interactive and simple.

+
+
+

One such hypermedia oriented library is htmx. Htmx will be the + focus of Part Two. + We show that you can, in fact, create many common “modern” UI features found in + sophisticated Single + Page Applications by instead using the hypermedia model.

+
+
+

And, it is refreshingly fun and simple to do so.

+
+
+
+

Hypermedia-Driven Applications

+
+

When building a web application with htmx the term Multi-Page Application applies + roughly, but it doesn’t fully characterize + the core of the application architecture. As you will see, htmx doesn’t need + to replace entire pages, and, in fact, an + htmx-based application can reside entirely within a single page. We don’t recommend + this practice, but it is + possible!

+
+
+

So it isn’t quite right to call web applications built with htmx "Multi-Page + Applications." What the older Web 1.0 MPA + approach and the newer hypermedia-oriented library powered applications have in common is + their use of hypermedia as + their core technology and architecture.

+
+
+

Therefore, we use the term Hypermedia-Driven Applications (HDAs) to describe both. +

+
+
+

This clarifies that the core distinction between these two approaches and the SPA approach + isn’t the number of pages + in the application, but rather the underlying system architecture.

+
+
+
+
Hypermedia-Driven Application (HDA)
+
+

A web application that uses hypermedia and hypermedia exchanges as + its primary + mechanism for communicating with a server.

+
+
+
+
+

So, what does an HDA look like up close?

+
+
+

Let’s look at an htmx-powered implementation of the simple JavaScript-powered button + above:

+
+
+
An Htmx Implementation
+
+
<button hx-get="/contacts/1" hx-target="#contact-ui"> (1)
     Fetch Contact
 </button>
-
-
-
-
    -
  1. -

    issues a GET request to /contacts/1, replacing the contact-ui.

    -
  2. -
-
-
-

As with the JavaScript powered button, this button has been annotated with some attributes. However, in -this case we do not have any JavaScript scripting.

-
-
-

Instead, we have declarative attributes much like the href attribute on anchor tags and the action attribute on -form tags. The hx-get attribute tells htmx: "`When the user clicks this button, issue a GET request to /contacts/1." -The hx-target attribute tells htmx: "`When the response returns, take the resulting HTML and place it into the element -with the id contact-ui."

-
-
-

Here we get to the crux of htmx and how it allows you to build Hypermedia-Driven Applications:

-
-
-

The HTTP response from the server is expected to be in HTML format, not JSON.

-
-
-

This htmx-powered button is exchanging hypermedia with the server, just like an anchor tag or form -might, and thus the interaction is still within the original hypermedia model of the web. Htmx is adding functionality -to this button (via JavaScript), but that functionality is augmenting HTML as a hypermedia. Htmx extends the hypermedia -system of the web, rather than replacing that hypermedia system with a totally different architecture.

-
-
-

Despite looking superficially similar to one another it turns out that this htmx-powered button and the JavaScript-based -button are using extremely different system architectures and, thus, approaches to web development.

-
-
-

As we walk through building a Hypermedia-Driven Application in this book, the differences between the two approaches -will become more and more apparent.

-
-
-
-
-

1.6. When Should You Use Hypermedia?

-
-

Hypermedia is often, though not always, a great choice for a web application.

-
-
-

Perhaps you are building a website or application that simply doesn’t need a huge amount of user-interactivity. There are -many useful web applications like this, and there is no shame in it! Applications like Amazon, eBay, any number of news -sites, shopping sites, message boards and so on don’t need a massive amount of interactivity to be effective: they are -mainly text and images, which is exactly what the web was designed for.

-
-
-

Perhaps your application adds most of its value on the server side, by coordinating users or by applying sophisticated -data analysis and then presenting it to a user. Perhaps your application adds value by simply sitting in front of a -well-designed database, with simple Create-Read-Update-Delete (CRUD) operations. Again, there is no shame in this!

-
-
-

In any of these cases, using a hypermedia approach would likely be a great choice: the interactivity needs of -these applications are not dramatic, and much of the value of these applications lives on the server side, rather than on the client side.

-
-
-

All of these applications are amenable to what Roy Fielding called “large-grain hypermedia data transfers”: you can simply -use anchor tags and forms, with responses that return entire HTML documents from requests, and things will work just fine. -This is exactly what the web was designed to do!

-
-
-

By adopting the hypermedia approach for these applications, you will save yourself a huge amount of client-side complexity -that comes with adopting the Single Page Application approach: there is no need for client-side routing, for managing -a client-side model, for hand-wiring in JavaScript logic, and so forth. The back button will “just work”. Deep linking -will “just work”. You will be able to focus your efforts on your server, where your application is actually adding value.

-
-
-

And, by layering htmx or another hypermedia-oriented library on top of this approach, you can address many of the usability -issues that come with vanilla HTML and take advantage of finer-grained hypermedia transfers. This opens up a whole slew of new -user interface and experience possibilities, making the set of applications that can be built using hypermedia much larger.

-
-
-

But more on that later.

-
-
-
-

1.7. When Shouldn’t You Use Hypermedia?

-
-

So, what about that not always? When isn’t hypermedia going to work well for an application?

-
-
-

One example that springs immediately to mind is an online spreadsheet application. In the case of a spreadsheet, -updating one cell could have a large number of cascading changes that need to be made across the entire sheet. Worse, -this might need to happen on every keystroke.

-
-
-

In this case we have a highly dynamic user interface without clear boundaries as to what might need to be updated given -a particular change. Introducing a hypermedia-style server round-trip on every cell change would hurt performance -tremendously.

-
-
-

This is simply not a situation amenable to the “large-grain hypermedia data transfer” approach of the web. For an -application like this we would certainly recommend looking into using a sophisticated client-side JavaScript approach.

-
-
-

However even in the case of an online spreadsheet there are likely areas where the hypermedia approach might help.

-
-
-

The spreadsheet application likely also has a settings page. And perhaps that settings page is amenable to -the hypermedia approach. If it is simply a set of relatively straight-forward forms that need to be persisted to the -server, the chances are good that hypermedia would, in fact, work great for this part of the app.

-
-
-

And, by adopting hypermedia for that part of your application, you might be able to simplify that part of the application -quite a bit. You could then save more of your application’s complexity budget for the core, complicated spreadsheet logic, -keeping the simple stuff simple.

-
-
-

Why waste all the complexity associated with a heavy JavaScript framework on something as simple as a settings page?

-
- -
-
-

1.8. Hypermedia: A Sophisticated, Modern System Architecture

-
-

Hypermedia is often regarded as an old and antiquated technology in web development circles, useful perhaps -for static websites but certainly not a realistic choice for modern, sophisticated web applications.

-
-
-

Seriously? Are we claiming that modern web applications can be built using it?

-
-
-

Yes, seriously.

-
-
-

Contrary to current popular opinion, hypermedia is an innovative and modern system architecture for building -applications, in some ways more modern than the prevailing Single Page Application approaches. In the remainder -of this book we will reintroduce you to the core, practical concepts of hypermedia and then demonstrate exactly how -you can take advantage of this system architecture in your own software.

-
-
-

In the coming chapters you will develop a firm understanding of all the benefits and techniques enabled by this approach. -We hope that, in addition, you will also become as passionate about it as we are.

-
-
-

This book is, in part, a plea that we “let the web be the web”, that we take the original architecture of the web -seriously, and that we consider the entire hypermedia system it makes available to us when we build applications -with it.

-
-
-
-
-
-

2. Components Of A Hypermedia System

-
-
-

A hypermedia system consists of a number of components:

-
-
-
    -
  • -

    A hypermedia, such as HTML

    -
  • -
  • -

    A network protocol, such as HTTP

    -
  • -
  • -

    A server that presents a hypermedia API responding to network requests with hypermedia responses

    -
  • -
  • -

    A client that properly interprets those responses

    -
  • -
-
-
-

In this chapter we will look at these components and their implementation in the context of the web.

-
-
-

Once we have reviewed the major components of the web as a hypermedia system, we will look at key insights developed by Roy -Fielding in his dissertation, “Architectural Styles and the Design of Network-based Software Architectures.” We will see where the -terms REpresenation State Transfer (REST), RESTful and Hypermedia As The Engine Of Application State (HATEOAS) come from, -and we will analyze these terms in the context of the web.

-
-
-

This should give you a stronger understanding of the theoretical basis of the web as a hypermedia system, how it is -supposed to fit together, and why Hypermedia-Driven Applications are RESTful, whereas JSON APIs — despite the way the -term REST is currently used in the industry — are not.

-
-
-

2.1. Components Of A Hypermedia System

-
-

The Hypermedia

-
-

The fundamental technology of a hypermedia system is a hypermedia that allows a -client and server to communicate with one another in a dynamic, non-linear fashion. Again, what makes a hypermedia -a hypermedia is the presence of hypermedia controls: elements in the hypermedia that allow users to select -non-linear actions within the hypermedia. Users of a hypermedia system can interact with the media in a manner beyond -simply reading from start to end.

-
-
-

We have already mentioned the two primary hypermedia controls in HTML, anchors and forms, which allow a browser to -present links and operations to a user through a browser.

-
-
-

In the case of HTML, these links and forms typically specify the target of their operations using Uniform Resource -Locators (URLs):

-
-
-
-
Uniform Resource Locator
-
-

A uniform resource locator is a textual string that refers to, or points to a location -on a network where a resource can be retrieved from, as well as the mechanism by which the resource can be retrieved.

-
-
-
-
-

A URL is a string consisting of various subcomponents:

-
-
-
URL Components
-
-
[scheme]://[userinfo]@[host]:[port][path]?[query]#[fragment]
-
-
-
-

Many of these subcomponents are not required, and are often omitted.

-
-
-

A typical URL might look like this:

-
-
-
A simple URL
-
-
https://hypermedia.systems/book
-
-
-
-

This particular URL is made up of the following components:

-
-
-
    -
  • -

    A protocol or scheme (in this case https)

    -
  • -
  • -

    A domain (in this case hypermedia.systems)

    -
  • -
  • -

    A path (in this case /book)

    -
  • -
-
-
-

This URL uniquely identifies a retrievable resource on the internet, to which an HTTP Request can be issued by -a hypermedia client that “speaks” HTTPS, such as a web browser. If this URL is found as the reference of a -hypermedia control within an HTML document, it implies that there is a hypermedia server on the other side of the -network that understands HTTPS as well, and that can respond to this request with a representation of the given -resource (or redirect you to another location, etc.)

-
-
-

Note that URLs are often not written out entirely within HTML. It is very common to see anchor tags that look like this, -for example:

-
-
-
A Simple Link
-
-
<a href="/book/contents/">Table Of Contents</a>
-
-
-
-

Here we have a relative hypermedia reference, where the protocol, host and port are implied to be that of the “current -document”, that is, the same as whatever the protocol and server were to retrieve the current HTML page. So, if this -link was found in an HTML document retrieved from https://hypermedia.systems/, then the implied URL for this anchor -would be https://hypermedia.systems/book/contents/.

-
-
-
-

Hypermedia Protocols

-
-

The hypermedia control (link) above tells a browser: “When a user clicks on this text, issue request to -https://hypermedia.systems/book/contents/ using the Hypertext Transfer Protocol”, or HTTP.

-
-
-

HTTP is the protocol used to transfer HTML (hypermedia) between browsers (hypermedia clients) and servers (hypermedia -servers) and, as such, is the key network technology that binds the distributed hypermedia system of the web together.

-
-
-

HTTP version 1.1 is a relatively simple network protocol, so lets take a look at what the GET request triggered by the anchor -tag would look like. This is the request that would be sent to the server found at hypermedia.systems, on port 80 -by default:

-
-
-
-
GET /book/contents/ HTTP/1.1
+                        
+
+
+
    +
  1. +

    issues a GET request to /contacts/1, replacing the + contact-ui.

    +
  2. +
+
+
+

As with the JavaScript powered button, this button has been annotated with some attributes. + However, in + this case we do not have any JavaScript scripting.

+
+
+

Instead, we have declarative attributes much like the href attribute on + anchor tags and the action attribute on + form tags. The hx-get attribute tells htmx: "`When the user clicks this button, + issue a GET request to /contacts/1." + The hx-target attribute tells htmx: "`When the response returns, take the + resulting HTML and place it into the element + with the id contact-ui."

+
+
+

Here we get to the crux of htmx and how it allows you to build Hypermedia-Driven + Applications:

+
+
+

The HTTP response from the server is expected to be in HTML format, not JSON.

+
+
+

This htmx-powered button is exchanging hypermedia with the server, just like an + anchor tag or form + might, and thus the interaction is still within the original hypermedia model of the web. + Htmx is adding functionality + to this button (via JavaScript), but that functionality is augmenting HTML as a + hypermedia. Htmx extends the hypermedia + system of the web, rather than replacing that hypermedia system with a totally + different architecture.

+
+
+

Despite looking superficially similar to one another it turns out that this htmx-powered + button and the JavaScript-based + button are using extremely different system architectures and, thus, approaches to web + development.

+
+
+

As we walk through building a Hypermedia-Driven Application in this book, the differences + between the two approaches + will become more and more apparent.

+
+
+
+
+

1.6. When Should You Use Hypermedia?

+
+

Hypermedia is often, though not always, a great choice for a web application.

+
+
+

Perhaps you are building a website or application that simply doesn’t need a huge + amount of user-interactivity. There are + many useful web applications like this, and there is no shame in it! Applications like Amazon, + eBay, any number of news + sites, shopping sites, message boards and so on don’t need a massive amount of + interactivity to be effective: they are + mainly text and images, which is exactly what the web was designed for.

+
+
+

Perhaps your application adds most of its value on the server side, by coordinating + users or by applying sophisticated + data analysis and then presenting it to a user. Perhaps your application adds value by simply + sitting in front of a + well-designed database, with simple Create-Read-Update-Delete (CRUD) operations. Again, there is + no shame in this!

+
+
+

In any of these cases, using a hypermedia approach would likely be a great choice: the + interactivity needs of + these applications are not dramatic, and much of the value of these applications lives on the + server side, rather than on the client side.

+
+
+

All of these applications are amenable to what Roy Fielding called “large-grain hypermedia + data transfers”: you can simply + use anchor tags and forms, with responses that return entire HTML documents from requests, and + things will work just fine. + This is exactly what the web was designed to do!

+
+
+

By adopting the hypermedia approach for these applications, you will save yourself a huge amount + of client-side complexity + that comes with adopting the Single Page Application approach: there is no need for client-side + routing, for managing + a client-side model, for hand-wiring in JavaScript logic, and so forth. The back button will + “just work”. Deep linking + will “just work”. You will be able to focus your efforts on your server, where your + application is actually adding value.

+
+
+

And, by layering htmx or another hypermedia-oriented library on top of this approach, you can + address many of the usability + issues that come with vanilla HTML and take advantage of finer-grained hypermedia transfers. + This opens up a whole slew of new + user interface and experience possibilities, making the set of applications that can be built + using hypermedia much larger.

+
+
+

But more on that later.

+
+
+
+

1.7. When Shouldn’t You Use Hypermedia?

+
+

So, what about that not always? When isn’t hypermedia going to work well for an + application?

+
+
+

One example that springs immediately to mind is an online spreadsheet application. In the case of + a spreadsheet, + updating one cell could have a large number of cascading changes that need to be made across the + entire sheet. Worse, + this might need to happen on every keystroke.

+
+
+

In this case we have a highly dynamic user interface without clear boundaries as to what might + need to be updated given + a particular change. Introducing a hypermedia-style server round-trip on every cell change would + hurt performance + tremendously.

+
+
+

This is simply not a situation amenable to the “large-grain hypermedia data transfer” + approach of the web. For an + application like this we would certainly recommend looking into using a sophisticated + client-side JavaScript approach.

+
+
+

However even in the case of an online spreadsheet there are likely areas where the + hypermedia approach might help.

+
+
+

The spreadsheet application likely also has a settings page. And perhaps that settings page + is amenable to + the hypermedia approach. If it is simply a set of relatively straight-forward forms that need to + be persisted to the + server, the chances are good that hypermedia would, in fact, work great for this part of the + app.

+
+
+

And, by adopting hypermedia for that part of your application, you might be able to simplify that + part of the application + quite a bit. You could then save more of your application’s complexity budget for + the core, complicated spreadsheet logic, + keeping the simple stuff simple.

+
+
+

Why waste all the complexity associated with a heavy JavaScript framework on something as simple + as a settings page?

+
+ +
+
+

1.8. Hypermedia: A Sophisticated, Modern + System Architecture

+
+

Hypermedia is often regarded as an old and antiquated technology in web development circles, + useful perhaps + for static websites but certainly not a realistic choice for modern, sophisticated web + applications.

+
+
+

Seriously? Are we claiming that modern web applications can be built using it?

+
+
+

Yes, seriously.

+
+
+

Contrary to current popular opinion, hypermedia is an innovative and modern + system architecture for building + applications, in some ways more modern than the prevailing Single Page Application + approaches. In the remainder + of this book we will reintroduce you to the core, practical concepts of hypermedia and then + demonstrate exactly how + you can take advantage of this system architecture in your own software.

+
+
+

In the coming chapters you will develop a firm understanding of all the benefits and techniques + enabled by this approach. + We hope that, in addition, you will also become as passionate about it as we are.

+
+
+

This book is, in part, a plea that we “let the web be the web”, that we take the + original architecture of the web + seriously, and that we consider the entire hypermedia system it makes available to us + when we build applications + with it.

+
+
+
+
+
+

2. Components Of A Hypermedia System

+
+
+

A hypermedia system consists of a number of components:

+
+
+
    +
  • +

    A hypermedia, such as HTML

    +
  • +
  • +

    A network protocol, such as HTTP

    +
  • +
  • +

    A server that presents a hypermedia API responding to network requests with hypermedia + responses

    +
  • +
  • +

    A client that properly interprets those responses

    +
  • +
+
+
+

In this chapter we will look at these components and their implementation in the context of the web. +

+
+
+

Once we have reviewed the major components of the web as a hypermedia system, we will look at key + insights developed by Roy + Fielding in his dissertation, “Architectural Styles and the Design of Network-based Software + Architectures.” We will see where the + terms REpresenation State Transfer (REST), RESTful and Hypermedia As The Engine Of Application State + (HATEOAS) come from, + and we will analyze these terms in the context of the web.

+
+
+

This should give you a stronger understanding of the theoretical basis of the web as a hypermedia + system, how it is + supposed to fit together, and why Hypermedia-Driven Applications are RESTful, whereas JSON + APIs — despite the way the + term REST is currently used in the industry — are not.

+
+
+

2.1. Components Of A Hypermedia System

+
+

The Hypermedia

+
+

The fundamental technology of a hypermedia system is a hypermedia that allows a + client and server to communicate with one another in a dynamic, non-linear fashion. Again, + what makes a hypermedia + a hypermedia is the presence of hypermedia controls: elements in the hypermedia + that allow users to select + non-linear actions within the hypermedia. Users of a hypermedia system can interact + with the media in a manner beyond + simply reading from start to end.

+
+
+

We have already mentioned the two primary hypermedia controls in HTML, anchors and forms, + which allow a browser to + present links and operations to a user through a browser.

+
+
+

In the case of HTML, these links and forms typically specify the target of their operations + using Uniform Resource + Locators (URLs):

+
+
+
+
Uniform Resource Locator
+
+

A uniform resource locator is a textual string that refers to, or points to + a location + on a network where a resource can be retrieved from, as well as the + mechanism by which the resource can be retrieved.

+
+
+
+
+

A URL is a string consisting of various subcomponents:

+
+
+
URL Components
+
+
[scheme]://[userinfo]@[host]:[port][path]?[query]#[fragment]
+
+
+
+

Many of these subcomponents are not required, and are often omitted.

+
+
+

A typical URL might look like this:

+
+
+
A simple URL
+
+
https://hypermedia.systems/book
+
+
+
+

This particular URL is made up of the following components:

+
+
+
    +
  • +

    A protocol or scheme (in this case https)

    +
  • +
  • +

    A domain (in this case hypermedia.systems)

    +
  • +
  • +

    A path (in this case /book)

    +
  • +
+
+
+

This URL uniquely identifies a retrievable resource on the internet, to which an + HTTP Request can be issued by + a hypermedia client that “speaks” HTTPS, such as a web browser. If this URL is + found as the reference of a + hypermedia control within an HTML document, it implies that there is a hypermedia + server on the other side of the + network that understands HTTPS as well, and that can respond to this request with a + representation of the given + resource (or redirect you to another location, etc.)

+
+
+

Note that URLs are often not written out entirely within HTML. It is very common to see + anchor tags that look like this, + for example:

+
+
+
A Simple Link
+
+
<a href="/book/contents/">Table Of Contents</a>
+
+
+
+

Here we have a relative hypermedia reference, where the protocol, host and port are + implied to be that of the “current + document”, that is, the same as whatever the protocol and server were to retrieve the + current HTML page. So, if this + link was found in an HTML document retrieved from + https://hypermedia.systems/, + then the implied URL for this anchor + would be + https://hypermedia.systems/book/contents/. +

+
+
+
+

Hypermedia Protocols

+
+

The hypermedia control (link) above tells a browser: “When a user clicks on this text, + issue request to + https://hypermedia.systems/book/contents/ using the Hypertext Transfer + Protocol”, or HTTP. +

+
+
+

HTTP is the protocol used to transfer HTML (hypermedia) between browsers (hypermedia + clients) and servers (hypermedia + servers) and, as such, is the key network technology that binds the distributed hypermedia + system of the web together.

+
+
+

HTTP version 1.1 is a relatively simple network protocol, so lets take a look at what the + GET request triggered by the anchor + tag would look like. This is the request that would be sent to the server found at + hypermedia.systems, on port 80 + by default:

+
+
+
+
GET /book/contents/ HTTP/1.1
 Accept: text/html,*/*
 Host: hypermedia.systems
-
-
-
-

The first line specifies that this is an HTTP GET request. It then specifies the path of the resource being -requested. Finally, it contains the HTTP version for this request.

-
-
-

After that are a series of HTTP Request Headers, individual lines of name/value pairs, separated by a colon, which provide -metadata that can be used by the server to determine exactly how to respond to the client request. In this case, -with the Accept header, the browser is saying it would prefer HTML as a response format, but will accept anything that -the server responds with.

-
-
-

Next, it has a Host header, which specifies the server that the request has been sent to. This is useful when multiple -domains are hosted on the same host.

-
-
-

An HTTP response from a server to this request might look something like this:

-
-
-
-
HTTP/1.1 200 OK
+                        
+
+
+

The first line specifies that this is an HTTP GET request. It then specifies the + path of the resource being + requested. Finally, it contains the HTTP version for this request.

+
+
+

After that are a series of HTTP Request Headers, individual lines of name/value + pairs, separated by a colon, which provide + metadata that can be used by the server to determine exactly how to respond to the + client request. In this case, + with the Accept header, the browser is saying it would prefer HTML as a + response format, but will accept anything that + the server responds with. +

+
+
+

Next, it has a Host header, which specifies the server that the request has been + sent to. This is useful when multiple + domains are hosted on the same host.

+
+
+

An HTTP response from a server to this request might look something like this:

+
+
+
+
HTTP/1.1 200 OK
 Content-Type: text/html; charset=utf-8
 Content-Length: 870
 Server: Werkzeug/2.0.2 Python/3.8.10
@@ -1696,597 +4975,786 @@ Date: Sat, 23 Apr 2022 18:27:55 GMT
   ...
 </body>
 </html>
-
-
-
-

In the first line, the HTTP Response specifies the HTTP version being used, followed by a response code of 200, -indicating that the given resource was found and that the request succeeded. This is followed by a string, OK that -corresponds to the response code. (The actual string doesn’t matter, it is the response code that tells the client -the result of a request, as we will discuss in more detail below.)

-
-
-

After the first line of the response, as with the HTTP Request, we see a series of Response Headers that provide -metadata to the client to assist in displaying the representation of the resource correctly.

-
-
-

Finally, we see some new HTML content. This content is the HTML representation of the requested resource, in this -case a table of contents of a book. The browser will use this HTML to replace the entire content in its display window, -showing the user this new page, and updating the address bar to reflect the new URL.

-
-
-
HTTP Methods
-
-

The anchor tag above issued an HTTP GET, where GET is the method of the request. The particular method -being used in an HTTP request is perhaps the most important piece of information about it, after the actual resource that -the request is directed at.

-
-
-

There are many methods available in HTTP; the ones of most practical importance to developers are the following:

-
-
-
-
GET
-
-

A GET request retrieves the representation of the specified resource. GET requests should not mutate data.

-
-
POST
-
-

A POST request submits data to the specified resource. This will often result in a mutation of state on the server.

-
-
PUT
-
-

A PUT request replaces the data of the specified resource. This results in a mutation of state on the server.

-
-
PATCH
-
-

A PATCH request replaces the data of the specified resource. This results in a mutation of state on the server.

-
-
DELETE
-
-

A DELETE request deletes the specified resource. This results in a mutation of state on the server.

-
-
-
-
-

These methods roughly line up with the “Create/Read/Update/Delete” or CRUD pattern found in many applications:

-
-
-
    -
  • -

    POST corresponds with Creating a resource

    -
  • -
  • -

    GET corresponds with Reading a resource

    -
  • -
  • -

    PUT and PATCH correspond with Updating a resource

    -
  • -
  • -

    DELETE corresponds, well, with Deleting a resource

    -
  • -
-
-
-

Note that this HTTP Action/CRUD correspondence is a rough rule of thumb for application development, the underlying RFCs -that specify them make no such connection and are often somewhat obscure. Here, for example, is the documentation -on the distinction between a POST and a PUT from RCF-2616:

-
-
-
-
-

The fundamental difference between the POST and PUT methods is highlighted by the different intent for the enclosed -representation. The target resource in a POST request is intended to handle the enclosed representation according to the -resource’s own semantics, whereas the enclosed representation in a PUT request is defined as replacing the state of the -target resource. Hence, the intent of PUT is idempotent and visible to intermediaries, even though the exact -effect is only known by the origin server.

-
-
-
-— RCF-2616
-https://www.rfc-editor.org/rfc/rfc2616#section-9.6 -
-
-
-

So, in plain terms, a POST can be handled by a server pretty much however it likes, whereas a PUT should be handled -as a “replacement” of the resource, although the language, once again allows the server to do pretty much whatever it -would like within the constraint of being idempotent.

-
-
-

This sort of academic language (and arguments around it) can be alienating to many web developers. While we feel it is -good to learn these concepts (e.g. idempotency) in depth, we also feel that requiring a PhD to build effective hypermedia -systems is unreasonable. Frankly, the academic and pedantic language around things like HTTP methods is one reason why -hypermedia has fallen on hard times.

-
-
-

In any event, in a properly structured HTML-based hypermedia system you should use an appropriate HTTP method for the operation a -particular hypermedia control performs: If a hypermedia control such as a button deletes a resource, for example, ideally -it should an HTTP DELETE request to do so.

-
- -
-
-
HTTP Response Codes
-
-

HTTP Request methods allow a client to tell a server what to do to a given resource. HTTP Responses contain -response codes, which tell a client what the result of the request was. HTTP response codes are numeric -values that are embedded in the HTTP response, as we saw above.

-
-
-

The most familiar response code for most web developers is probably 404, which stands for “Not Found”. This -is the response code that is returned by web servers when a resource that does not exist is requested from them.

-
-
-

HTTP breaks response codes up into various categories:

-
-
-
-
100-199
-
-

Informational responses that provide information about how the server is processing the response

-
-
200-299
-
-

Successful responses indicating that the request succeeded

-
-
300-399
-
-

Redirection responses indicating that the request should be sent to some other URL

-
-
400-499
-
-

Client error responses indicating that the client made some sort of bad request (e.g. asking for something that didn’t -exist in the case of 404 errors)

-
-
500-599
-
-

Server error responses indicating that the server encountered an error internally as it attempted to respond to the request

-
-
-
-
-

Within each of these categories there are multiple response codes for specific situations.

-
-
-

Here are some of the more common or interesting ones:

-
-
-
-
200 OK
-
-

The HTTP request succeeded

-
-
301 Moved Permanently
-
-

The URL for the requested resource has moved to a new location permanently, and the new URL will be provided in -the Location response header

-
-
302 Found
-
-

The URL for the requested resource has moved to a new location temporarily, and the new URL will be provided in -the Location response header

-
-
303 See Other
-
-

The URL for the requested resource has moved to a new location, and the new URL will be provided in -the Location response header. Additionally, this new URL should be retrieved with a GET request.

-
-
401 Unauthorized
-
-

The client is not yet authenticated (yes, authenticated, despite the name) and must be authenticated -to retrieve the given resource.

-
-
403 Forbidden
-
-

The client does not have access to this resource.

-
-
404 Not Found
-
-

The server cannot find the requested resource.

-
-
500 Internal Server Error
-
-

The server encountered an error when attempting to process the response.

-
-
-
-
-

There are some fairly subtle differences between HTTP response codes. (And, to be honest, some ambiguities between them.) -The difference between a 302 redirect and a 303 redirect, for example, is that the former will issue the request to the -new URL using the same HTTP method, were the latter will always use a GET. A small, but often crucial difference, -as we will see later in the book.

-
-
-

Nonetheless, a well crafted hypermedia system will take advantage of both HTTP methods and HTTP response codes to create a sensible -hypermedia API. You do not want to build a hypermedia system that uses a POST method for all requests and responds -with 200 OK for every response. Some JSON Data APIs built on top of HTTP do exactly this!

-
-
-

When building a Hypermedia-Driven Application, you want, instead, to go “with the grain” of the web and use HTTP methods -and response codes as they were designed to be used.

-
-
-
-
Caching HTTP Responses
-
-

A constraint of REST (and, therefore, a feature of HTTP) is the notion of Caching responses: a server can indicate to -a client (as well as intermediary HTTP servers) that a given response can be cached for future requests to the same -URL.

-
-
-

The cache behavior of an HTTP response from a server can be indicated with the Cache-Control response header. This -header can have a number of different values indicating the cacheability of a given response. If, for example, the header -contains the value max-age=60, this indicates that a client may cache this response for 60 seconds, and need not issue -another HTTP request for that resource until that time limit has expired.

-
-
-

Another important caching-related response header is Vary. This response header can be used to indicate exactly what -headers in an HTTP Request form the unique identifier for a cached result. This becomes important to allow the browser -to correctly cache content in situations where a particular header affects the form of the server response. A common -pattern in htmx-powered applications is to use a custom header set by htmx, HX-Request, to differentiate between -“normal” web requests and requests submitted by htmx. To properly cache the response to these requests, the HX-Request -request header must be indicated by the Vary response header.

-
-
-

A full discussion of caching HTTP responses is beyond the scope of this chapter; see -the MDN Article on HTTP Caching if you would like to know more on the topic.

-
-
-
-
-

Hypermedia Servers

-
-

Hypermedia servers are any server that can respond to an HTTP request with an HTTP response. Because HTTP is so simple, -this means that nearly any programming language can be used to build a hypermedia server. There are a vast number of -libraries available for building HTTP-based hypermedia servers in nearly every programming language imaginable.

-
-
-

This is one of the best aspects of adopting hypermedia as your primary technology for building a web application: it removes -the pressure of adopting JavaScript as a back-end technology. In contrast, if you decide to adopt a JavaScript-heavy -Single Page Application-based front end, and you use JSON Data APIs, you will feel significant pressure to adopt -JavaScript on the back end.

-
-
-

In this latter situation, you already have a ton of code written in JavaScript. Why maintain two separate code bases in -two different languages? Why not create reusable domain logic on the client-side as well as the server-side? Now that -JavaScript has excellent server-side technologies available like node and deno, why not just a single language for -everything?

-
-
-

In contrast, using a hypermedia-based front end gives you a lot more freedom in picking the back end technology you want -to use. Your decision can be based on the domain of your application, what languages and server software you are familiar -with or are passionate about, or just what you feel like trying out.

-
-
-

You certainly aren’t writing your server-side logic in HTML! And every major programming language has at least one good -web framework and templating library that can be used to handle HTTP requests cleanly.

-
-
-

Perhaps if you are doing something in big data, perhaps you’d like to use Python, which has tremendous support for that -domain.

-
-
-

Perhaps if you are doing AI work, perhaps you’d like to use Lisp, leaning on a language with a long history in that -area of research.

-
-
-

Maybe you are a functional programming enthusiast and want to use OCaml or Haskell. Perhaps you just really like Julia or -Nim.

-
-
-

These are all perfectly valid reasons for choosing a particular server-side technology!

-
-
-

By using hypermedia as your system architecture, you are freed up to adopt any of these choices. There simply isn’t a -large JavaScript code base on the front end pressuring you to adopt JavaScript on the back end.

-
- -
-
-

Hypermedia Clients

-
-

We now come to the final major component in a hypermedia system: the hypermedia client. Hypermedia clients are software -that understand how to interpret a particular hypermedia, and the hypermedia controls within it, properly. The canonical -example, of course, is the web browser, which understand HTML and can present it to a user to interact with. Web browsers -are incredibly sophisticated pieces of software. (So sophisticated, in fact, that they are often re-purposed away from -being a hypermedia client, to being a sort of cross-platform virtual machine for launching Single Page Applications.)

-
-
-

Browsers aren’t the only hypermedia clients out there, however. In the last section of this book we will look at -Hyperview, a mobile-oriented hypermedia. One of the outstanding features of Hyperview is that it doesn’t simply provide -a hypermedia, HXML, but also provides a working hypermedia client for that hypermedia. This makes building a proper -Hypermedia-Driven Application with Hyperview extremely easy.

-
-
-

A crucial feature of a hypermedia system is what is known as the uniform interface. We discuss this concept in depth -in the next section on REST. What is often ignored in discussions about hypermedia is how important the hypermedia -client is in taking advantage of this uniform interface. A hypermedia client must know how to properly interpret and -present hypermedia controls found in a hypermedia response from a hypermedia server for the whole hypermedia system -to hang together. Without a sophisticated client that can do this, hypermedia controls and a hypermedia-based API are -much less useful.

-
-
-

This is one reason why JSON APIs have rarely adopted hypermedia controls successfully: JSON APIs are typically consumed -by code that is expecting a fixed-format and isn’t designed to be a hypermedia client. For clients like this, the -power of hypermedia controls embedded within an API response is irrelevant and often simply annoying:

-
-
-
-
-

The short answer to this question is that HATEOAS isn’t a good fit for most modern use cases for APIs. That is why -after almost 20 years, HATEOAS still hasn’t gained wide adoption among developers. GraphQL on the other hand is spreading -like wildfire because it solves real-world problems.

-
-
-
-— Freddie Karlbom
-https://techblog.commercetools.com/graphql-and-rest-level-3-hateoas-70904ff1f9cf -
-
-
-

HATEOAS will be described in more detail below, but the take away here is that a good hypermedia client is a necessary -component within a larger hypermedia system.

-
-
-
-
-

2.2. REST

-
-

Now that we have reviewed the major components of a hypermedia system, it’s time to look more deeply into the concept of -REST. The term “REST” comes from Chapter 5 of Roy Fielding’s PhD dissertation on the architecture -of the web. Fielding wrote his dissertation at U.C. Irvine, after having helped build much of the infrastructure of the early -web, including the Apache web server. Roy was attempting to formalize and describe the novel distributed computing system -that he had helped to build.

-
-
-

We are going to focus in on what we feel is the most important section of Fielding’s dissertation, from a web development -perspective: Section 5.1. This section contains the core concepts (Fielding calls them constraints) of Representational -State Transfer, or REST.

-
-
-

Before we get into the muck, however, it is important to understand that Fielding discusses REST as a network architecture, -that is an entirely different way of architecting a distributed system. And a novel one that should be contrasted with -earlier distributed systems.

-
-
-

It is also important to emphasize that, at the time Fielding wrote his dissertation, JSON APIs and AJAX did not exist. -He was describing the early web, with HTML being transferred over HTTP by early browsers, as a hypermedia system.

-
-
-

Today, in a strange turn of events, the term “REST” is mainly associated with JSON Data APIs, rather than with HTML -and hypermedia. This becomes extremely humorous once you realize that the vast majority of JSON Data APIs aren’t -RESTful, and, in fact can’t be RESTful, since they aren’t using a natural hypermedia format.

-
-
-

To re-emphasise: REST, as coined by Fielding, describes the pre-JSON API web, and letting go of the current, common -usage of the term as “JSON API” is necessary to develop a proper understanding of it.

-
-
-

The “Constraints” of REST

-
-

In his dissertation, Fielding defines various “constraints” to describe how a RESTful system must behave. This approach -can feel a little round-about and difficult to follow for many people, but it is an appropriate approach for an academic -dissertation. Given a bit of time thinking about the constraints he outlines, and some concrete examples, it will -become easy to understand if a given system actually satisfies the architectural requirements of REST or not.

-
-
-

Here are the constraints of REST, which are outlined in Section 5.1 of his dissertation:

-
-
-
    -
  • -

    It is a client-server architecture (section 5.1.2)

    -
  • -
  • -

    It must be stateless (section 5.1.3) that is, every request contains all information necessary to respond to that request

    -
  • -
  • -

    It must allow for caching (section 5.1.4)

    -
  • -
  • -

    It must have a uniform interface (section 5.1.5)

    -
  • -
  • -

    It is a layered system (section 5.1.6)

    -
  • -
  • -

    Optionally, can allow for Code-On-Demand (section 5.1.7), that is, scripting.

    -
  • -
-
-
-

Let’s go through each of these constrains in turn and discuss them in detail, looking at how (and to what extent) the web -satisfies each of them.

-
-
-
-

The Client-Server Constraint

-
-

See Section 5.1.2 for the -Client-Server constraint.

-
-
-

Obviously, the REST model Fielding was describing involved both clients (browsers, in the case of the web) and servers (such -as the Apache Web Server he had been working on) communicating via a network connection. This was the context of his -work: he was describing the network architecture of the World Wide Web, and contrasting it with earlier architectures, -notably thick-client networking models such as the Common Object Request Broker Architecture (CORBA).

-
-
-

It should be obvious that any web application, regardless of how it is designed, will satisfy this requirement.

-
-
-
-

The Statelessness Constraint

-
-

See Section 5.1.3 for the Stateless constraint.

-
-
-

As described by Fielding, a RESTful system is stateless: every request should encapsulate all information necessary to -respond to that request, with no side state or context stored on either the client or the server.

-
-
-

In practice, for many web applications today, we actually violate this constraint: it is common to establish a -session cookie that acts as a unique identifier for a given user and that is sent along with every request. While this -session cookie is, by itself, not stateful (it is sent with every request), it is typically -used as a key to look up information stored on the server, in what is usually termed "`the session`."

-
-
-

This session information is typically stored in some sort of shared storage across multiple web servers, holding things -like the current users email or id, their roles, partially created domain objects, caches, and so forth.

-
-
-

This violation of the Statelessness REST architectural constraint has proven to be useful for building web applications -and, for the most part, does not appear to have had a significant impact on the overall flexibility of the approach. But -it is worth bearing in mind that even Web 1.0 applications often violate the purity of REST in the interest of pragmatic -trade-offs.

-
-
-

It should be noted, however, that sessions do cause additional operational complexity headaches when deploying hypermedia -servers, which now may need to have shared access to the session state information stored across an entire cluster. So -Fielding was correct in pointing out that an ideal RESTful system, one that did not violate this constraint, would, -indeed, be simpler and therefore more robust.

-
-
-
-

The Caching Constraint

-
-

See Section 5.1.4 for the Caching constraint.

-
-
-

This constraint states that a RESTful system should support the notion of caching, with explicit information on the -cache-ability of responses for future requests of the same resource. This allows both clients as well as intermediary -servers between a given client and final server to cache the results of a given request.

-
-
-

As we discussed above, HTTP has a sophisticated caching mechanism via Response headers that is often overlooked or -underutilized when building hypermedia applications. Given the existence of this functionality, however, it is -easy to see how this constraint is satisfied by the web.

-
-
-
-

The Uniform Interface Constraint

-
-

Now we come to the most interesting and, in our opinion, innovative constraint in REST: that of the uniform interface. -This constraint is the source of much of the flexibility and simplicity of a hypermedia system, so we are going to -spend a lot of time on it.

-
-
-

See Section 5.1.5 for the Uniform Interface -constraint.

-
-
-

In this section, Fielding says:

-
-
-
-
-
-
-

The central feature that distinguishes the REST architectural style from other network-based styles is its emphasis on -a uniform interface between components…​ In order to obtain a uniform interface, multiple architectural constraints -are needed to guide the behavior of components. REST is defined by four interface constraints: identification of -resources; manipulation of resources through representations; self-descriptive messages; and, hypermedia as the engine -of application state

-
-
-
-
-
-— Roy Fielding
-Architectural Styles and the Design of Network-based Software Architectures -
-
-
-

So we have four additional sub-constraints that, taken together, form the Uniform Interface constraint.

-
-
-
Identification of Resources
-
-

In a RESTful system, resources should have a unique identifier. Today the concept of Universal Resource Locators (URLs) is -common, but at the time of Fielding’s writing they were still relatively new and novel.

-
-
-

What might be more interesting today is the notion of a resource, thus being identified: in a RESTful system, any sort of -data that can be referenced, that is, the target of a hypermedia reference, is considered a resource. URLs, though common -enough today, end up solving the very complex problem of uniquely identifying any and every resource on the internet.

-
-
-
-
Manipulation of Resources Through Representations
-
-

In a RESTful system, representations of the resource are transferred between clients and servers. These -representations can contain both data and metadata about the request (such as “control data” like an HTTP -method or response code). A particular data format or media type may be used to present a given resource to a client, -and that media type can be negotiated between the client and the server.

-
-
-

We saw this latter aspect of the uniform interface in the Accept header in the requests above.

-
-
-
-
Self-Descriptive Messages
-
-

The Self-Descriptive Messages constraint, combined with the next one, HATEOAS, form what we consider to be the core of -the Uniform Interface, of REST and why hypermedia provides such a powerful system architecture.

-
-
-

The Self-Descriptive Messages constraint requires that, in a RESTful system, messages must be self-describing.

-
-
-

This means that all information necessary to both display and also operate on the data being represented must be -present in the response. In a properly RESTful system, there can be no additional “side” information necessary for -client to transform a response from a server into a useful user interface. Everything must “be in” the message itself, -in the form of hypermedia controls.

-
-
-

This might sound a little abstract, lets look at a concrete example.

-
-
-

Consider two different potential responses from of an HTTP server for the URL https://example.com/contacts/42.

-
-
-

Both responses will return information about a contact, but they will take very different forms.

-
-
-

The first implementation returns an HTML representation:

-
-
-
-
<html lang="en">
+                        
+
+
+

In the first line, the HTTP Response specifies the HTTP version being used, followed by a + response code of 200, + indicating that the given resource was found and that the request succeeded. This is + followed by a string, OK that + corresponds to the response code. (The actual string doesn’t matter, it is the + response code that tells the client + the result of a request, as we will discuss in more detail below.)

+
+
+

After the first line of the response, as with the HTTP Request, we see a series of + Response Headers that provide + metadata to the client to assist in displaying the representation of the resource + correctly.

+
+
+

Finally, we see some new HTML content. This content is the HTML representation of + the requested resource, in this + case a table of contents of a book. The browser will use this HTML to replace the entire + content in its display window, + showing the user this new page, and updating the address bar to reflect the new URL.

+
+
+
HTTP Methods
+
+

The anchor tag above issued an HTTP GET, where GET is the + method of the request. The particular method + being used in an HTTP request is perhaps the most important piece of information about + it, after the actual resource that + the request is directed at.

+
+
+

There are many methods available in HTTP; the ones of most practical importance to + developers are the following:

+
+
+
+
GET
+
+

A GET request retrieves the representation of the specified resource. GET + requests should not mutate data.

+
+
POST
+
+

A POST request submits data to the specified resource. This will often result in + a mutation of state on the server.

+
+
PUT
+
+

A PUT request replaces the data of the specified resource. This results in a + mutation of state on the server.

+
+
PATCH
+
+

A PATCH request replaces the data of the specified resource. This results in a + mutation of state on the server.

+
+
DELETE
+
+

A DELETE request deletes the specified resource. This results in a mutation of + state on the server.

+
+
+
+
+

These methods roughly line up with the “Create/Read/Update/Delete” + or CRUD pattern found in many applications:

+
+
+
    +
  • +

    POST corresponds with Creating a resource

    +
  • +
  • +

    GET corresponds with Reading a resource

    +
  • +
  • +

    PUT and PATCH correspond with Updating a resource

    +
  • +
  • +

    DELETE corresponds, well, with Deleting a resource

    +
  • +
+
+
+

Note that this HTTP Action/CRUD correspondence is a rough rule of thumb for application + development, the underlying RFCs + that specify them make no such connection and are often somewhat obscure. Here, for + example, is the documentation + on the distinction between a POST and a PUT from RCF-2616:

+
+
+
+
+

The fundamental difference between the POST and PUT methods is highlighted by the + different intent for the enclosed + representation. The target resource in a POST request is intended to handle the + enclosed representation according to the + resource’s own semantics, whereas the enclosed representation in a PUT + request is defined as replacing the state of the + target resource. Hence, the intent of PUT is idempotent and visible to + intermediaries, even though the exact + effect is only known by the origin server.

+
+
+
+ — RCF-2616
+ https://www.rfc-editor.org/rfc/rfc2616#section-9.6 +
+
+
+

So, in plain terms, a POST can be handled by a server pretty much however it + likes, whereas a PUT should be handled + as a “replacement” of the resource, although the language, once again allows + the server to do pretty much whatever it + would like within the constraint of being idempotent.

+
+
+

This sort of academic language (and arguments around it) can be alienating to many web + developers. While we feel it is + good to learn these concepts (e.g. idempotency) in depth, we also feel that requiring a + PhD to build effective hypermedia + systems is unreasonable. Frankly, the academic and pedantic language around things like + HTTP methods is one reason why + hypermedia has fallen on hard times.

+
+
+

In any event, in a properly structured HTML-based hypermedia system you should use an + appropriate HTTP method for the operation a + particular hypermedia control performs: If a hypermedia control such as a button + deletes a resource, for example, ideally + it should an HTTP DELETE request to do so.

+
+ +
+
+
HTTP Response Codes
+
+

HTTP Request methods allow a client to tell a server what to do to a given + resource. HTTP Responses contain + response codes, which tell a client what the result of the request was. HTTP + response codes are numeric + values that are embedded in the HTTP response, as we saw above. +

+
+
+

The most familiar response code for most web developers is probably 404, + which stands for “Not Found”. This + is the response code that is returned by web servers when a resource that does not exist + is requested from them.

+
+
+

HTTP breaks response codes up into various categories:

+
+
+
+
100-199
+
+

Informational responses that provide information about how the server is + processing the response

+
+
200-299
+
+

Successful responses indicating that the request succeeded

+
+
300-399
+
+

Redirection responses indicating that the request should be sent to some other + URL

+
+
400-499
+
+

Client error responses indicating that the client made some sort of bad request + (e.g. asking for something that didn’t + exist in the case of 404 errors)

+
+
500-599
+
+

Server error responses indicating that the server encountered an error internally + as it attempted to respond to the request

+
+
+
+
+

Within each of these categories there are multiple response codes for specific + situations.

+
+
+

Here are some of the more common or interesting ones:

+
+
+
+
200 OK
+
+

The HTTP request succeeded

+
+
301 Moved Permanently
+
+

The URL for the requested resource has moved to a new location permanently, and + the new URL will be provided in + the Location response header

+
+
302 Found
+
+

The URL for the requested resource has moved to a new location temporarily, and + the new URL will be provided in + the Location response header

+
+
303 See Other
+
+

The URL for the requested resource has moved to a new location, and the new URL + will be provided in + the Location response header. Additionally, this new URL should be + retrieved with a GET request.

+
+
401 Unauthorized
+
+

The client is not yet authenticated (yes, authenticated, despite the name) and + must be authenticated + to retrieve the given resource.

+
+
403 Forbidden
+
+

The client does not have access to this resource.

+
+
404 Not Found
+
+

The server cannot find the requested resource.

+
+
500 Internal Server Error
+
+

The server encountered an error when attempting to process the response.

+
+
+
+
+

There are some fairly subtle differences between HTTP response codes. (And, to be honest, + some ambiguities between them.) + The difference between a 302 redirect and a 303 redirect, for + example, is that the former will issue the request to the + new URL using the same HTTP method, were the latter will always use a GET. + A small, but often crucial difference, + as we will see later in the book.

+
+
+

Nonetheless, a well crafted hypermedia system will take advantage of both HTTP methods + and HTTP response codes to create a sensible + hypermedia API. You do not want to build a hypermedia system that uses a + POST method for all requests and responds + with 200 OK for every response. Some JSON Data APIs built on top of HTTP do + exactly this!

+
+
+

When building a Hypermedia-Driven Application, you want, instead, to go “with the + grain” of the web and use HTTP methods + and response codes as they were designed to be used.

+
+
+
+
Caching HTTP Responses
+
+

A constraint of REST (and, therefore, a feature of HTTP) is the notion of Caching + responses: a server can indicate to + a client (as well as intermediary HTTP servers) that a given response can be cached for + future requests to the same + URL.

+
+
+

The cache behavior of an HTTP response from a server can be indicated with the + Cache-Control response header. This + header can have a number of different values indicating the cacheability of a given + response. If, for example, the header + contains the value max-age=60, this indicates that a client may cache this + response for 60 seconds, and need not issue + another HTTP request for that resource until that time limit has expired.

+
+
+

Another important caching-related response header is Vary. This response + header can be used to indicate exactly what + headers in an HTTP Request form the unique identifier for a cached result. This becomes + important to allow the browser + to correctly cache content in situations where a particular header affects the form of + the server response. A common + pattern in htmx-powered applications is to use a custom header set by htmx, + HX-Request, to differentiate between + “normal” web requests and requests submitted by htmx. To properly cache the + response to these requests, the HX-Request + request header must be indicated by the Vary response header.

+
+
+

A full discussion of caching HTTP responses is beyond the scope of this chapter; see + the MDN Article on + HTTP Caching if you would like to know more on the topic.

+
+
+
+
+

Hypermedia Servers

+
+

Hypermedia servers are any server that can respond to an HTTP request with an HTTP response. + Because HTTP is so simple, + this means that nearly any programming language can be used to build a hypermedia server. + There are a vast number of + libraries available for building HTTP-based hypermedia servers in nearly every programming + language imaginable.

+
+
+

This is one of the best aspects of adopting hypermedia as your primary technology for + building a web application: it removes + the pressure of adopting JavaScript as a back-end technology. In contrast, if you decide to + adopt a JavaScript-heavy + Single Page Application-based front end, and you use JSON Data APIs, you will feel + significant pressure to adopt + JavaScript on the back end.

+
+
+

In this latter situation, you already have a ton of code written in JavaScript. Why maintain + two separate code bases in + two different languages? Why not create reusable domain logic on the client-side as well as + the server-side? Now that + JavaScript has excellent server-side technologies available like node and deno, why not just + a single language for + everything?

+
+
+

In contrast, using a hypermedia-based front end gives you a lot more freedom in picking the + back end technology you want + to use. Your decision can be based on the domain of your application, what languages and + server software you are familiar + with or are passionate about, or just what you feel like trying out.

+
+
+

You certainly aren’t writing your server-side logic in HTML! And every major + programming language has at least one good + web framework and templating library that can be used to handle HTTP requests cleanly.

+
+
+

Perhaps if you are doing something in big data, perhaps you’d like to use Python, which + has tremendous support for that + domain.

+
+
+

Perhaps if you are doing AI work, perhaps you’d like to use Lisp, leaning on a language + with a long history in that + area of research.

+
+
+

Maybe you are a functional programming enthusiast and want to use OCaml or Haskell. Perhaps + you just really like Julia or + Nim.

+
+
+

These are all perfectly valid reasons for choosing a particular server-side technology!

+
+
+

By using hypermedia as your system architecture, you are freed up to adopt any of these + choices. There simply isn’t a + large JavaScript code base on the front end pressuring you to adopt JavaScript on the back + end.

+
+ +
+
+

Hypermedia Clients

+
+

We now come to the final major component in a hypermedia system: the hypermedia client. + Hypermedia clients are software + that understand how to interpret a particular hypermedia, and the hypermedia controls within + it, properly. The canonical + example, of course, is the web browser, which understand HTML and can present it to a user + to interact with. Web browsers + are incredibly sophisticated pieces of software. (So sophisticated, in fact, that they are + often re-purposed away from + being a hypermedia client, to being a sort of cross-platform virtual machine for launching + Single Page Applications.)

+
+
+

Browsers aren’t the only hypermedia clients out there, however. In the last section of + this book we will look at + Hyperview, a mobile-oriented hypermedia. One of the outstanding features of Hyperview is + that it doesn’t simply provide + a hypermedia, HXML, but also provides a working hypermedia client for that + hypermedia. This makes building a proper + Hypermedia-Driven Application with Hyperview extremely easy.

+
+
+

A crucial feature of a hypermedia system is what is known as the uniform interface. + We discuss this concept in depth + in the next section on REST. What is often ignored in discussions about hypermedia is how + important the hypermedia + client is in taking advantage of this uniform interface. A hypermedia client must know how + to properly interpret and + present hypermedia controls found in a hypermedia response from a hypermedia server for the + whole hypermedia system + to hang together. Without a sophisticated client that can do this, hypermedia controls and a + hypermedia-based API are + much less useful.

+
+
+

This is one reason why JSON APIs have rarely adopted hypermedia controls successfully: JSON + APIs are typically consumed + by code that is expecting a fixed-format and isn’t designed to be a hypermedia client. + For clients like this, the + power of hypermedia controls embedded within an API response is irrelevant and often simply + annoying:

+
+
+
+
+

The short answer to this question is that HATEOAS isn’t a good fit for most modern + use cases for APIs. That is why + after almost 20 years, HATEOAS still hasn’t gained wide adoption among developers. + GraphQL on the other hand is spreading + like wildfire because it solves real-world problems.

+
+
+
+ — Freddie Karlbom
+ https://techblog.commercetools.com/graphql-and-rest-level-3-hateoas-70904ff1f9cf +
+
+
+

HATEOAS will be described in more detail below, but the take away here is that a good + hypermedia client is a necessary + component within a larger hypermedia system.

+
+
+
+
+

2.2. REST

+
+

Now that we have reviewed the major components of a hypermedia system, it’s time to look + more deeply into the concept of + REST. The term “REST” comes from Chapter 5 of Roy Fielding’s PhD dissertation + on the architecture + of the web. Fielding wrote his dissertation at U.C. Irvine, after having helped build much of + the infrastructure of the early + web, including the Apache web server. Roy was attempting to formalize and describe the novel + distributed computing system + that he had helped to build.

+
+
+

We are going to focus in on what we feel is the most important section of Fielding’s + dissertation, from a web development + perspective: Section 5.1. This section contains the core concepts (Fielding calls them + constraints) of Representational + State Transfer, or REST.

+
+
+

Before we get into the muck, however, it is important to understand that Fielding discusses REST + as a network architecture, + that is an entirely different way of architecting a distributed system. And a novel one that + should be contrasted with + earlier distributed systems.

+
+
+

It is also important to emphasize that, at the time Fielding wrote his dissertation, JSON APIs + and AJAX did not exist. + He was describing the early web, with HTML being transferred over HTTP by early browsers, as a + hypermedia system.

+
+
+

Today, in a strange turn of events, the term “REST” is mainly associated with JSON + Data APIs, rather than with HTML + and hypermedia. This becomes extremely humorous once you realize that the vast majority of JSON + Data APIs aren’t + RESTful, and, in fact can’t be RESTful, since they aren’t using a natural + hypermedia format.

+
+
+

To re-emphasise: REST, as coined by Fielding, describes the pre-JSON API web, and + letting go of the current, common + usage of the term as “JSON API” is necessary to develop a proper understanding of + it.

+
+
+

The “Constraints” of REST

+
+

In his dissertation, Fielding defines various “constraints” to describe how a + RESTful system must behave. This approach + can feel a little round-about and difficult to follow for many people, but it is an + appropriate approach for an academic + dissertation. Given a bit of time thinking about the constraints he outlines, and some + concrete examples, it will + become easy to understand if a given system actually satisfies the architectural + requirements of REST or not.

+
+
+

Here are the constraints of REST, which are outlined in Section 5.1 of his dissertation:

+
+
+
    +
  • +

    It is a client-server architecture (section 5.1.2)

    +
  • +
  • +

    It must be stateless (section 5.1.3) that is, every request contains all information + necessary to respond to that request

    +
  • +
  • +

    It must allow for caching (section 5.1.4)

    +
  • +
  • +

    It must have a uniform interface (section 5.1.5)

    +
  • +
  • +

    It is a layered system (section 5.1.6)

    +
  • +
  • +

    Optionally, can allow for Code-On-Demand (section 5.1.7), that is, scripting.

    +
  • +
+
+
+

Let’s go through each of these constrains in turn and discuss them in detail, looking + at how (and to what extent) the web + satisfies each of them.

+
+
+
+

The Client-Server Constraint

+
+

See Section + 5.1.2 for the + Client-Server constraint.

+
+
+

Obviously, the REST model Fielding was describing involved both clients (browsers, + in the case of the web) and servers (such + as the Apache Web Server he had been working on) communicating via a network connection. + This was the context of his + work: he was describing the network architecture of the World Wide Web, and contrasting it + with earlier architectures, + notably thick-client networking models such as the Common Object Request Broker Architecture + (CORBA).

+
+
+

It should be obvious that any web application, regardless of how it is designed, will satisfy + this requirement.

+
+
+
+

The Statelessness Constraint

+
+

See Section + 5.1.3 for the Stateless constraint.

+
+
+

As described by Fielding, a RESTful system is stateless: every request should encapsulate all + information necessary to + respond to that request, with no side state or context stored on either the client or the + server.

+
+
+

In practice, for many web applications today, we actually violate this constraint: it is + common to establish a + session cookie that acts as a unique identifier for a given user and that is sent + along with every request. While this + session cookie is, by itself, not stateful (it is sent with every request), it is typically + used as a key to look up information stored on the server, in what is usually termed "`the + session`." +

+
+
+

This session information is typically stored in some sort of shared storage across multiple + web servers, holding things + like the current users email or id, their roles, partially created domain objects, caches, + and so forth.

+
+
+

This violation of the Statelessness REST architectural constraint has proven to be useful for + building web applications + and, for the most part, does not appear to have had a significant impact on the overall + flexibility of the approach. But + it is worth bearing in mind that even Web 1.0 applications often violate the purity of REST + in the interest of pragmatic + trade-offs.

+
+
+

It should be noted, however, that sessions do cause additional operational complexity + headaches when deploying hypermedia + servers, which now may need to have shared access to the session state information stored + across an entire cluster. So + Fielding was correct in pointing out that an ideal RESTful system, one that did not violate + this constraint, would, + indeed, be simpler and therefore more robust.

+
+
+
+

The Caching Constraint

+
+

See Section + 5.1.4 for the Caching constraint.

+
+
+

This constraint states that a RESTful system should support the notion of caching, with + explicit information on the + cache-ability of responses for future requests of the same resource. This allows both + clients as well as intermediary + servers between a given client and final server to cache the results of a given request.

+
+
+

As we discussed above, HTTP has a sophisticated caching mechanism via Response headers that + is often overlooked or + underutilized when building hypermedia applications. Given the existence of this + functionality, however, it is + easy to see how this constraint is satisfied by the web.

+
+
+
+

The Uniform Interface Constraint

+
+

Now we come to the most interesting and, in our opinion, innovative constraint in REST: that + of the uniform interface. + This constraint is the source of much of the flexibility and simplicity of + a hypermedia system, so we are going to + spend a lot of time on it.

+
+
+

See Section + 5.1.5 for the Uniform Interface + constraint.

+
+
+

In this section, Fielding says:

+
+
+
+
+
+
+

The central feature that distinguishes the REST architectural style from + other network-based styles is its emphasis on + a uniform interface between components…​ In order to obtain a + uniform interface, multiple architectural constraints + are needed to guide the behavior of components. REST is defined by four + interface constraints: identification of + resources; manipulation of resources through representations; + self-descriptive messages; and, hypermedia as the engine + of application state

+
+
+
+
+
+ — Roy Fielding
+ Architectural Styles and the Design of Network-based Software Architectures +
+
+
+

So we have four additional sub-constraints that, taken together, form the Uniform Interface + constraint.

+
+
+
Identification of Resources
+
+

In a RESTful system, resources should have a unique identifier. Today the concept of + Universal Resource Locators (URLs) is + common, but at the time of Fielding’s writing they were still relatively new and + novel.

+
+
+

What might be more interesting today is the notion of a resource, thus being + identified: in a RESTful system, any sort of + data that can be referenced, that is, the target of a hypermedia reference, is + considered a resource. URLs, though common + enough today, end up solving the very complex problem of uniquely identifying any and + every resource on the internet.

+
+
+
+
Manipulation of Resources Through + Representations
+
+

In a RESTful system, representations of the resource are transferred between + clients and servers. These + representations can contain both data and metadata about the request (such as + “control data” like an HTTP + method or response code). A particular data format or media type may be used to + present a given resource to a client, + and that media type can be negotiated between the client and the server.

+
+
+

We saw this latter aspect of the uniform interface in the Accept header in + the requests above.

+
+
+
+
Self-Descriptive Messages
+
+

The Self-Descriptive Messages constraint, combined with the next one, HATEOAS, form what + we consider to be the core of + the Uniform Interface, of REST and why hypermedia provides such a powerful system + architecture.

+
+
+

The Self-Descriptive Messages constraint requires that, in a RESTful system, messages + must be self-describing.

+
+
+

This means that all information necessary to both display and also + operate on the data being represented must be + present in the response. In a properly RESTful system, there can be no additional + “side” information necessary for + client to transform a response from a server into a useful user interface. Everything + must “be in” the message itself, + in the form of hypermedia controls.

+
+
+

This might sound a little abstract, lets look at a concrete example.

+
+
+

Consider two different potential responses from of an HTTP server for the URL + https://example.com/contacts/42. +

+
+
+

Both responses will return information about a contact, but they will take very different + forms.

+
+
+

The first implementation returns an HTML representation:

+
+
+
+
<html lang="en">
 <head>
 <h1>Joe Smith</h1>
 <div>
@@ -2299,100 +5767,124 @@ in the form of hypermedia controls.

</main> </body> </html>
-
-
-
-

The second implementation returns a JSON representation:

-
-
-
-
{
+                            
+
+
+

The second implementation returns a JSON representation:

+
+
+
+
{
   "name": "Joe Smith",
   "email": "joe@example.org",
   "status": "Active"
 }
-
-
-
-

What can we say about the differences between these two responses?

-
-
-

One thing that may initially jump out at you is that the JSON representation is smaller than the HTML -representation. Fielding notes exactly this trade-off when using a RESTful architecture:

-
-
-
-
-

The trade-off, though, is that a uniform interface degrades efficiency, since information is transferred in a -standardized form rather than one which is specific to an application’s needs.

-
-
-
-— Roy Fielding
-Architectural Styles and the Design of Network-based Software Architectures -
-
-
-

So REST trades off representational efficiency for other goals.

-
-
-

To understand these other goals, first notice that the HTML representation has a hyperlink in it to navigate to a page -to archive the contact. The JSON representation, in contrast, does not have this link.

-
-
-

What are the ramifications of this fact for a client of the JSON API?

-
-
-

What this means is that the JSON API client must know in advance exactly what other URLs (and request methods) are -available for working with the contact information. If the JSON client is able to update this contact in some way, it -must know how to do so from some source of information external to the JSON message. Is if the contact has a different -status, say “Archived”, does this change the allowable actions? If so, what are the new allowable actions?

-
-
-

The source of all this information might be API documentation, word of mouth or, if the developer controls both the server -and the client, internal knowledge. But it is outside the message.

-
-
-

The hypermedia (or HTML) client, on the other hand, needs only to know how to render the given HTML. It doesn’t need to understand -what actions are available for this contact: they are simply encoded within the HTML itself as hypermedia controls. It doesn’t need to -understand what the status field means or, in fact, what a contact even is!

-
-
-

The browser, our hypermedia client, simply renders the HTML and allows the user, who presumably understands the concept -of a Contact, to make a decision on what action to pursue from the actions made available in the representation.

-
-
-

This difference between the two responses demonstrates the crux of REST and hypermedia, what makes them so powerful - and flexible: clients (that is, web browsers) don’t need to understand anything about the underlying resources being -represented.

-
-
-

Browsers only (only! As if it is easy!) need to understand how to parse and display hypermedia, in this case HTML. This -gives hypermedia-based systems unprecedented flexibility in dealing with changes to both the backing representations and -to the system itself.

-
-
-
-
Hypermedia As The Engine of Application State (HATEOAS)
-
-

The final sub-constraint on the Uniform Interface is that, in a RESTful system, hypermedia should be “the engine of -application state”. This is sometimes abbreviated as “HATEOAS”, although Fielding prefers to use the terminology -“the hypermedia constraint” when discussing it.

-
-
-

This constraint is closely related to the previous self-describing message constraint. Let us consider again the two different -implementations of the end point /contacts/42, one returning HTML and one returning JSON. Let’s update the situation -such that the contact identified by this URL has now been archived.

-
-
-

What do our responses look like?

-
-
-

The first implementation returns the following HTML:

-
-
-
-
<html lang="en">
+                            
+
+
+

What can we say about the differences between these two responses?

+
+
+

One thing that may initially jump out at you is that the JSON representation is smaller + than the HTML + representation. Fielding notes exactly this trade-off when using a RESTful architecture: +

+
+
+
+
+

The trade-off, though, is that a uniform interface degrades efficiency, since + information is transferred in a + standardized form rather than one which is specific to an application’s + needs.

+
+
+
+ — Roy Fielding
+ Architectural Styles and the Design of Network-based Software Architectures +
+
+
+

So REST trades off representational efficiency for other goals.

+
+
+

To understand these other goals, first notice that the HTML representation has a + hyperlink in it to navigate to a page + to archive the contact. The JSON representation, in contrast, does not have this link. +

+
+
+

What are the ramifications of this fact for a client of the JSON API?

+
+
+

What this means is that the JSON API client must know in advance exactly what + other URLs (and request methods) are + available for working with the contact information. If the JSON client is able to update + this contact in some way, it + must know how to do so from some source of information external to the JSON + message. Is if the contact has a different + status, say “Archived”, does this change the allowable actions? If so, what + are the new allowable actions?

+
+
+

The source of all this information might be API documentation, word of mouth or, if the + developer controls both the server + and the client, internal knowledge. But it is outside the message.

+
+
+

The hypermedia (or HTML) client, on the other hand, needs only to know how to render the + given HTML. It doesn’t need to understand + what actions are available for this contact: they are simply encoded within the + HTML itself as hypermedia controls. It doesn’t need to + understand what the status field means or, in fact, what a contact even is!

+
+
+

The browser, our hypermedia client, simply renders the HTML and allows the user, who + presumably understands the concept + of a Contact, to make a decision on what action to pursue from the actions made + available in the representation.

+
+
+

This difference between the two responses demonstrates the crux of REST and hypermedia, + what makes them so powerful + and flexible: clients (that is, web browsers) don’t need to understand + anything about the underlying resources being + represented.

+
+
+

Browsers only (only! As if it is easy!) need to understand how to parse and display + hypermedia, in this case HTML. This + gives hypermedia-based systems unprecedented flexibility in dealing with changes to both + the backing representations and + to the system itself.

+
+
+
+
Hypermedia As The Engine of + Application State (HATEOAS)
+
+

The final sub-constraint on the Uniform Interface is that, in a RESTful system, + hypermedia should be “the engine of + application state”. This is sometimes abbreviated as “HATEOAS”, + although Fielding prefers to use the terminology + “the hypermedia constraint” when discussing it.

+
+
+

This constraint is closely related to the previous self-describing message constraint. + Let us consider again the two different + implementations of the end point /contacts/42, one returning HTML and one + returning JSON. Let’s update the situation + such that the contact identified by this URL has now been archived.

+
+
+

What do our responses look like?

+
+
+

The first implementation returns the following HTML:

+
+
+
+
<html lang="en">
 <head>
 <h1>Joe Smith</h1>
 <div>
@@ -2405,70 +5897,89 @@ such that the contact identified by this URL has now been archived.

</main> </body> </html>
-
-
-
-

The second implementation returns the following JSON representation:

-
-
-
-
{
+                            
+
+
+

The second implementation returns the following JSON representation:

+
+
+
+
{
   "name": "Joe Smith",
   "email": "joe@example.org",
   "status": "Archived"
 }
-
-
-
-

The important point to notice here is that, by virtue of being a self-describing message, the HTML response now shows that -the “Archive” operation is no longer available, and a new “Unarchive” operation has become available. The HTML representation -of the contact encodes the state of the application (that is, exactly what can and cannot be done with this particular -representation) in a way that the JSON representation does not.

-
-
-

A client interpreting the JSON response must, again, understand not only the general concept of a Contact, -but also specifically what the “status” field with the value “Archived” means. It must know exactly what operations -are available on an “Archived” contact, to appropriately display them to an end user. The state of the application, -in this situation is not encoded in the response, but rather in a mix of raw data and side channel information such as -API documentation.

-
-
-

Furthermore, in the majority of front end SPA frameworks today, this contact information would live in memory in a -JavaScript object representing a model of the contact. The DOM would be updated based on changes to this model, that -is, the DOM would “react” to changes to this backing JavaScript model.

-
-
-

This approach is certainly not using Hypermedia As The Engine Of Application State: rather, it is using a javascript -model as the engine of application state, and synchronizing that model with a server and with the browser.

-
-
-

With the HTML approach, the Hypermedia is, indeed, The Engine Of Application State: there is no additional model on the -client side, and all state is expressed directly in the hypermedia, in this case HTML. As state changes on the server, -it is reflected in the representation (that is, HTML) sent back to the client. The hypermedia client (a browser) doesn’t know -anything about contacts, what the concept of “Archiving” is, or anything else about the particular domain model for this -response: it simply knows how to render HTML.

-
-
-

Because a hypermedia client doesn’t need to know anything about the server model beyond how to render hypermedia to -a client, it is incredibly flexible with respect to the representations it receives and displays to users.

-
-
-
-
HATEOAS & API Churn
-
-

Because this last point is so important to understand in order to appreciate the flexibility of hypermedia, let’s look -at a practical example of it in action: consider a situation where a new feature has added the web application of these -two end points. This feature allows you to send a message to a given Contact.

-
-
-

How would this change each of the two responses from the server?

-
-
-

The HTML representation might now look like this:

-
-
-
-
<html lang="en">
+                            
+
+
+

The important point to notice here is that, by virtue of being a self-describing message, + the HTML response now shows that + the “Archive” operation is no longer available, and a new + “Unarchive” operation has become available. The HTML representation + of the contact encodes the state of the application (that is, exactly what can + and cannot be done with this particular + representation) in a way that the JSON representation does not.

+
+
+

A client interpreting the JSON response must, again, understand not only the general + concept of a Contact, + but also specifically what the “status” field with the value + “Archived” means. It must know exactly what operations + are available on an “Archived” contact, to appropriately display them to an + end user. The state of the application, + in this situation is not encoded in the response, but rather in a mix of raw data and + side channel information such as + API documentation.

+
+
+

Furthermore, in the majority of front end SPA frameworks today, this contact information + would live in memory in a + JavaScript object representing a model of the contact. The DOM would be updated based on + changes to this model, that + is, the DOM would “react” to changes to this backing JavaScript model.

+
+
+

This approach is certainly not using Hypermedia As The Engine Of Application + State: rather, it is using a javascript + model as the engine of application state, and synchronizing that model with a server and + with the browser.

+
+
+

With the HTML approach, the Hypermedia is, indeed, The Engine Of Application State: there + is no additional model on the + client side, and all state is expressed directly in the hypermedia, in this case HTML. + As state changes on the server, + it is reflected in the representation (that is, HTML) sent back to the client. The + hypermedia client (a browser) doesn’t know + anything about contacts, what the concept of “Archiving” is, or anything + else about the particular domain model for this + response: it simply knows how to render HTML.

+
+
+

Because a hypermedia client doesn’t need to know anything about the server model + beyond how to render hypermedia to + a client, it is incredibly flexible with respect to the representations it receives and + displays to users.

+
+
+
+
HATEOAS & API Churn
+
+

Because this last point is so important to understand in order to appreciate the + flexibility of hypermedia, let’s look + at a practical example of it in action: consider a situation where a new feature has + added the web application of these + two end points. This feature allows you to send a message to a given Contact.

+
+
+

How would this change each of the two responses from the server?

+
+
+

The HTML representation might now look like this:

+
+
+
+
<html lang="en">
 <head>
 <h1>Joe Smith</h1>
 <div>
@@ -2482,399 +5993,503 @@ two end points.  This feature allows you to send a message to a given Contact.
-
-
-
-

The JSON representation, on the other hand, might look like this:

-
-
-
-
{
+                            
+
+
+

The JSON representation, on the other hand, might look like this:

+
+
+
+
{
   "name": "Joe Smith",
   "email": "joe@example.org",
   "status": "Active"
 }
-
-
-
-

Note that, once again, the JSON representation is unchanged. There is no indication of this new functionality. Instead, -a client must know about this change, presumably via some shared documentation between the client and the server.

-
-
-

Contrast this with the HTML response. Because of the uniform interface of the RESTful model and, in particular, -because we are using Hypermedia As The Engine of Application State, no such exchange of documentation is necessary! Instead, -the client (a browser) simply renders the new HTML with this operation in it, making this operation available for the end user -without any additional coding changes.

-
-
-

A pretty neat trick!

-
-
-

Now, in this case, if the JSON client is not properly updated, the error state is relatively benign: a new bit of functionality -is simply not made available to users. But consider a more severe change to the API: what if the archive functionality -was removed? Or what if the URLs or the HTTP methods for these operations changed in some way?

-
-
-

In this case, the JSON client may be broken in a much more serious manner.

-
-
-

The HTML response, however, would simply be updated to exclude the removed options or to update the URLs used for them. Clients -would see the new HTML, display it properly, and allow users to select whatever the new set of operations happens to be. Once -again, the uniform interface of REST has proven to be extremely flexible: despite a potentially radically new layout -for our hypermedia API, clients continue to keep working.

-
-
-

An important fact falls out of this: because of this flexibility, hypermedia APIs do not have the versioning headaches -that JSON Data APIs do.

-
-
-

Once a Hypermedia-Driven Application has been “entered into” (that is, loaded through some entry point URL), all functionality -and resources are surfaced through self-describing messages. Therefore, there is no need to exchange documentation with -the client: the client simply renders the hypermedia (in this case HTML) and everything works out. When a change occurs, -there is no need to create a new version of the API: clients simply retrieve updated hypermedia, which encodes the new -operations and resources in it, and display it to users to work with.

-
-
-
-
-

Layered System

-
-

The final “required” constraint on a RESTful system that we will consider is The Layered System constraint. This constraint can be found in Section 5.1.6 of Fielding’s dissertation.

-
-
-

To be frank, after the excitement of the uniform interface constraint, the “layered system” constraint is a bit of a -let down. But it is still worth understanding and it is actually utilized effectively by The web. The constraint -requires that a RESTful architecture be “layered”, allowing for multiple servers to act as intermediaries between -a client and the eventual “source of truth” server.

-
-
-

These intermediary servers can act as proxies, transform intermediate requests and responses and so forth.

-
-
-

A common modern example if this layering feature of REST is the use of Content Delivery Networks (CDNs) to deliver unchanging -static assets to clients more quickly, by storing the response from the origin server in intermediate servers more -closely located to the client making a request.

-
-
-

This allows content to be delivered more quickly to the end user and reduces load on the origin server.

-
-
-

Nothing nearly as exciting for web application developers as the uniform interface, at least in our opinion, but useful -nonetheless.

-
-
-
-

An Optional Constraint: Code-On-Demand

-
-

We called The Layered System constraint the final "required" constraint because -Fielding mentions one additional constraint on a RESTful system. This Code On Demand constraint is somewhat awkwardly described as "optional" (Section 5.1.7).

-
-
-

In this section, Fielding says:

-
-
-
-
-

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.

-
-
-
-— Roy Fielding
-Architectural Styles and the Design of Network-based Software Architectures -
-
-
-

So, scripting was and is a native aspect of the original RESTful model of the web, and thus -should of course be allowed in a Hypermedia-Driven Application.

-
-
-

However, in a Hypermedia-Driven Application the presence of scripting should not change the fundamental networking -model: hypermedia should still be the engine of application state, server communication should still consist of -hypermedia exchanges rather than, for example, JSON data exchanges, and so on.

-
-
-

Today, unfortunately, the scripting layer of the web, JavaScript, is quite often used to replace, rather than augment -the hypermedia model. We will elaborate in a later chapter what scripting that does not replace the underlying hypermedia -system of the web looks like.

-
-
-
-
-

2.3. Conclusion

-
-

After this deep dive into Chapter 5 of Roy Fielding’s dissertation, we hope you have much better understanding of REST, -and in particular, of the uniform interface and HATEOAS. We hope you can see why these characteristics make hypermedia -systems so flexible.

-
-
-

If you didn’t really appreciate what REST and HATEOAS meant before now, don’t feel bad: it took some of us over a decade of -working in web development, and building a hypermedia-oriented library to boot, to realize just how -special HTML, hypermedia and the web is!

-
-
-
-
-
-

3. Effective HTML

-
-
-

If you’re here, you can probably write some HTML. -The web is the hypermedia system after all, the one this book will spend the most time with, -and HTML is its format.

-
-
-

As with every aspect of the web, it has been exapted and reinterpreted by web developers in myriad ways. -Is it a document format? -Is it for applications? -Is it a rendering system? -Is it (gasp!) a programming language? -These are the contenders in the Eternal Debate of the web development world, -and none will ever win because none of them are right.

-
-
-
-
-

When I say hypertext, I mean the simultaneous presentation of information and controls such that the information becomes the affordance through which the user (or automaton) obtains choices and selects actions.

-
-
-
-— Roy Fielding
-https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven#comment-718 -
-
-
-

HTML, as well as hypermedia before and after it, removes the distinction between the information being accessed and the application used to access it. HTML is for documents, insofar as you’re willing to adopt a broad definition of “document”, and it is for applications, ones that are interwoven with the data they process.

-
-
-

HTML is a hypermedium.

-
-
-

In this chapter, we’ll learn about some HTML practices that are either skipped over, -or shoved in people’s faces as semantic or accessible without understanding why. -We’ll learn why HTML is far cooler than a programming language.

-
-
-

3.1. Why relearn HTML?

-
-

Have you noticed that a lot of websites are bad?

-
-
-
    -
  • -

    Pages are bloated with <div> soup, and stylesheets are big as a result of trying to select elements in that mess. The result is slow loading times.https://almanac.httparchive.org/en/2020/markup Other than <div> being the most common element, the HTTP Archive Web Almanac found that 0.06% of pages surveyed in 2020 contained the nonexistent <h7> element. 0.0015% used <h8>.

    -
  • -
  • -

    Websites, including websites containing public data or results of publicly-funded research, are impossible to scrape programmatically.

    -
  • -
  • -

    So-called MVPs (minimum viable product) are released in open beta while being completely unusable by vast swathes of people — UX not just buggy, but nonexistent.https://adrianroselli.com/2022/11/accessibility-gaps-in-mvps.html Is an inaccessible product “viable”?

    -
  • -
  • -

    Search engines have a hard time extracting useful information from a page, and rank that page lower as a result.

    -
  • -
-
-
-

There are several disparate reasons for these issues, but the neglect of HTML is a significant one.

-
-
-

People who write HTML are referred to in Web platform specifications as "`authors`." -This sells our lot short by quite a bit, however; -from day to day, the average web developer might take on the role of -typesetter, copy editor, graphic designer, and of course, programmer. -Thus, anyone in the business of making websites should take HTML seriously.

-
-
-

The way most of us write HTML (and likely the way many of us learned it) is a very tight feedback loop: -we write something, Alt-Tab to the browser to see if it works, and go back to edit. -It’s an amazingly fast and enjoyable way to build, but the way it’s often practiced has a significant flaw: -If it looks right, it gets shipped. -The developer is focusing almost exclusively to their own UI needs. -Any other way of using a website becomes an afterthought.

-
-
-

Thus, if you care about machine readability, or human readability, or page weight, what you should do is test.

-
-
-

Did you think we were going to talk about semantic HTML? Keep reading for that, but alas, it’s no substitute for testing. Test manually. Test automatically. Test with screenreaders, test with a keyboard, test on different browsers and hardware, run linters (while coding and/or in CI). Read the markup — the markup being sent to browsers, not the templates and code you write. Know exactly what any type of hypermedia client is doing in any circumstance.

-
-
-

So where does HTML and the s-word come in?

-
-
-

Easy. Writing good, spec-compliant HTML lets browsers do a bunch of work for you. Furthermore, even when they don’t, it makes it easier to write scripts that do. Fewer issues will be found during testing and you can release faster. When issues do come up, you can often fix them more easily by rewriting semantic HTML as opposed to heaping JavaScript and ARIA attributes over everything.

-
-
-

Good HTML will not absolve you from doing your job, but it will make it easier.

-
-
-
-

3.2. The S word

-
-
-
-

Gretchen, stop trying to make fetch happen! It’s not going to happen!

-
-
-
-— _Mean Girls_ (2004) -
-
-
-

In human language, a word can only have a certain meaning if some group of people know it to have that meaning. -You could define your own words and use them, the aforementioned Ted Nelson and company really liked to, but it’s difficult. -Providing a definition when you use it is not enough; -you also need an audience interested enough to read and remember that definition.

-
-
-

As this applies to computer languages too, any hypermedia format which lets documents define their own elements is an infinite universe of “fetch”-es to make happen. -Thus, let us renounce the schematamania that plagues many hypermedia discussions. -Instead, when we talk about semantics, we refer to the simple act of using elements in accordance with their agreed-upon meaning.

-
-
-

Instead of being extensible through schemas or namespaces, or whatever DTDs are, HTML is extended in two ways:

-
-
-
    -
  • -

    Ad-hoc extensibility. HTML is a fault-tolerant language, choosing to ignore things it doesn’t recognize instead of throwing errors. -This means you can use unspecified attributes. -htmx relies on this heavily. -If extensions see common acceptance, they might even be incorporated into specifications! (We can dream.)

    -
  • -
  • -

    Specified extension points. Things like classes, <meta> tags, custom elements and data- attributes are made for custom data. -They can be used to build meta-languages embedded in HTML.

    -
  • -
-
-
-

This might seem like a downgrade, and an anxiety-inducing one at that. -Think of the name collisions! -Indeed, it has some significant compromises, but it also correctly acknowledges that defining custom semantics without prior agreement between all parties is a fiction. -A flexible format --not an infinity of namespaces with URLs pointing to nothing --is “software design on the scale of decades”.

-
-
-

Let’s be real, after all --out of all the sites using “Open Graph” tags, how many use the appropriate prefix attribute? How many of their developers even know the prefix attribute exists?

-
-
-
-

3.3. HTML is for humans first

-
-

An HTML file is not a program that produces a human-readable document. -It is the document.

-
-
-

Treat HTML semantics not as a technical specification, but as something close to a style guide. -The meanings of HTML elements and attributes -(besides a few like <meta> and <script>) -are derived from conventions of human communication: -intractably fuzzy, and culturally dependent. -They have quite specific meanings in the spec, -but these meanings can be used sarcastically, hyperbolically, …​

-
-
-

The relationship between the content and the markup means good HTML is actually quite expensive. -If a site has a developer/author split, authors will need to work closely with developers or know HTML themselves to produce markup that reflects their intended meaning. -This is rarely feasible, except for sites written, designed and coded by one person. -Furthermore, for internationalized sites, content in different languages being injected into the same elements can degrade markup quality as stylistic conventions differ between languages. -Dishearteningly, but understandably, it’s an expense few organizations can spare.

-
-
-

Thus, we don’t demand that every site makes the "most semantic" HTML. -What’s most important is to avoid wrong HTML---it’s better to fall back on a more generic element than be precisely incorrect. -Sometimes, div is fine.

-
-
-

If you have the resources, however, putting more care in your HTML will produce a more polished site. -Much like style guides, well-written semantic HTML gives prestige to a document, even when few people notice it. -It can also make your HTML easier to maintain.

-
-
-

Good HTML is also good for non-visual usage. -When working on accessibility, you may find yourself trying and failing to please screen readers. -Especially if ARIA is involved, it can be extremely frustrating to do what seems like the right thing and have assistive tools not do what you expect. -What helps with this frustration is to stop treating hypermedia exchanges as machine-to-machine communication. -When browsers misbehave, we complain, but most of us don’t just down tools and concede: -"I did as the spec told me; it’s not my fault that the website is broken, my work here is done." -We get in there and fix it. -So, instead of banging your head against a wall, focus on people, not the tools they use.

-
-
-

Don’t write HTML for browsers.
-Don’t write HTML for assistive tools.
-Don’t write HTML for validators.
-HTML is not for them.
-HTML is for humans.

-
- -
-
-

3.4. Using the spec

-
-

While the big spec document with all the algorithms is probably better for smugly linking to people in chatrooms, -don’t miss out on the developer-friendly version at https://html.spec.whatwg.org/dev/.

-
-
-

For readers with better things to do, section 4 features a list of all tags in HTML. -It includes what tags mean, where they can occur, and what they are allowed to contain. -It even tells you when you’re allowed to leave out closing tags!

-
-
-
-
<!doctype html>
+                            
+
+
+

Note that, once again, the JSON representation is unchanged. There is no indication of + this new functionality. Instead, + a client must know about this change, presumably via some shared documentation + between the client and the server.

+
+
+

Contrast this with the HTML response. Because of the uniform interface of the RESTful + model and, in particular, + because we are using Hypermedia As The Engine of Application State, no such exchange of + documentation is necessary! Instead, + the client (a browser) simply renders the new HTML with this operation in it, making + this operation available for the end user + without any additional coding changes.

+
+
+

A pretty neat trick!

+
+
+

Now, in this case, if the JSON client is not properly updated, the error state is + relatively benign: a new bit of functionality + is simply not made available to users. But consider a more severe change to the API: + what if the archive functionality + was removed? Or what if the URLs or the HTTP methods for these operations changed in + some way?

+
+
+

In this case, the JSON client may be broken in a much more serious manner.

+
+
+

The HTML response, however, would simply be updated to exclude the removed options or to + update the URLs used for them. Clients + would see the new HTML, display it properly, and allow users to select whatever the new + set of operations happens to be. Once + again, the uniform interface of REST has proven to be extremely flexible: despite a + potentially radically new layout + for our hypermedia API, clients continue to keep working.

+
+
+

An important fact falls out of this: because of this flexibility, hypermedia APIs do + not have the versioning headaches + that JSON Data APIs do.

+
+
+

Once a Hypermedia-Driven Application has been “entered into” (that is, loaded + through some entry point URL), all functionality + and resources are surfaced through self-describing messages. Therefore, there is no need + to exchange documentation with + the client: the client simply renders the hypermedia (in this case HTML) and everything + works out. When a change occurs, + there is no need to create a new version of the API: clients simply retrieve updated + hypermedia, which encodes the new + operations and resources in it, and display it to users to work with.

+
+
+
+
+

Layered System

+
+

The final “required” constraint on a RESTful system that we will consider is The + Layered System constraint. This constraint can be found in Section + 5.1.6 of Fielding’s dissertation.

+
+
+

To be frank, after the excitement of the uniform interface constraint, the “layered + system” constraint is a bit of a + let down. But it is still worth understanding and it is actually utilized effectively by The + web. The constraint + requires that a RESTful architecture be “layered”, allowing for multiple servers + to act as intermediaries between + a client and the eventual “source of truth” server.

+
+
+

These intermediary servers can act as proxies, transform intermediate requests and responses + and so forth.

+
+
+

A common modern example if this layering feature of REST is the use of Content Delivery + Networks (CDNs) to deliver unchanging + static assets to clients more quickly, by storing the response from the origin server in + intermediate servers more + closely located to the client making a request.

+
+
+

This allows content to be delivered more quickly to the end user and reduces load on the + origin server.

+
+
+

Nothing nearly as exciting for web application developers as the uniform interface, at least + in our opinion, but useful + nonetheless.

+
+
+
+

An Optional Constraint: Code-On-Demand

+
+

We called The Layered System constraint the final "required" constraint because + Fielding mentions one additional constraint on a RESTful system. This Code On Demand + constraint is somewhat awkwardly described as "optional" (Section 5.1.7).

+
+
+

In this section, Fielding says:

+
+
+
+
+

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.

+
+
+
+ — Roy Fielding
+ Architectural Styles and the Design of Network-based Software Architectures +
+
+
+

So, scripting was and is a native aspect of the original RESTful model of the web, and thus + should of course be allowed in a Hypermedia-Driven Application.

+
+
+

However, in a Hypermedia-Driven Application the presence of scripting should not + change the fundamental networking + model: hypermedia should still be the engine of application state, server communication + should still consist of + hypermedia exchanges rather than, for example, JSON data exchanges, and so on.

+
+
+

Today, unfortunately, the scripting layer of the web, JavaScript, is quite often used to + replace, rather than augment + the hypermedia model. We will elaborate in a later chapter what scripting that does not + replace the underlying hypermedia + system of the web looks like.

+
+
+
+
+

2.3. Conclusion

+
+

After this deep dive into Chapter 5 of Roy Fielding’s dissertation, we hope you have much + better understanding of REST, + and in particular, of the uniform interface and HATEOAS. We hope you can see why these + characteristics make hypermedia + systems so flexible.

+
+
+

If you didn’t really appreciate what REST and HATEOAS meant before now, don’t feel + bad: it took some of us over a decade of + working in web development, and building a hypermedia-oriented library to boot, to realize just + how + special HTML, hypermedia and the web is!

+
+
+
+
+
+

3. Effective HTML

+
+
+

If you’re here, you can probably write some HTML. + The web is the hypermedia system after all, the one this book will spend the most time + with, + and HTML is its format.

+
+
+

As with every aspect of the web, it has been exapted and reinterpreted by web developers in myriad + ways. + Is it a document format? + Is it for applications? + Is it a rendering system? + Is it (gasp!) a programming language? + These are the contenders in the Eternal Debate of the web development world, + and none will ever win because none of them are right.

+
+
+
+
+

When I say hypertext, I mean the simultaneous presentation of information and controls such + that the information becomes the affordance through which the user (or automaton) obtains + choices and selects actions.

+
+
+
+ — Roy Fielding
+ https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven#comment-718 +
+
+
+

HTML, as well as hypermedia before and after it, removes the distinction between the information + being accessed and the application used to access it. HTML is for documents, insofar as you’re + willing to adopt a broad definition of “document”, and it is for applications, ones that + are interwoven with the data they process.

+
+
+

HTML is a hypermedium.

+
+
+

In this chapter, we’ll learn about some HTML practices that are either skipped over, + or shoved in people’s faces as semantic or accessible without understanding + why. + We’ll learn why HTML is far cooler than a programming language.

+
+
+

3.1. Why relearn HTML?

+
+

Have you noticed that a lot of websites are bad?

+
+
+
    +
  • +

    Pages are bloated with <div> soup, and stylesheets are big as a result + of trying to select elements in that mess. The result is slow loading times.https://almanac.httparchive.org/en/2020/markup Other + than <div> being the most common element, the HTTP Archive Web + Almanac found that 0.06% of pages surveyed in 2020 contained the nonexistent + <h7> element. 0.0015% used <h8>.

    +
  • +
  • +

    Websites, including websites containing public data or results of publicly-funded + research, are impossible to scrape programmatically.

    +
  • +
  • +

    So-called MVPs (minimum viable product) are released in open beta while being completely + unusable by vast swathes of people — UX not just buggy, but + nonexistent.https://adrianroselli.com/2022/11/accessibility-gaps-in-mvps.html + Is an inaccessible product “viable”?

    +
  • +
  • +

    Search engines have a hard time extracting useful information from a page, and rank that + page lower as a result.

    +
  • +
+
+
+

There are several disparate reasons for these issues, but the neglect of HTML is a significant + one.

+
+
+

People who write HTML are referred to in Web platform specifications as "`authors`." + This sells our lot short by quite a bit, however; + from day to day, the average web developer might take on the role of + typesetter, copy editor, graphic designer, and of course, programmer. + Thus, anyone in the business of making websites should take HTML seriously.

+
+
+

The way most of us write HTML (and likely the way many of us learned it) is a very tight feedback + loop: + we write something, Alt-Tab to the browser to see if it works, and go back to edit. + It’s an amazingly fast and enjoyable way to build, but the way it’s often practiced + has a significant flaw: + If it looks right, it gets shipped. + The developer is focusing almost exclusively to their own UI needs. + Any other way of using a website becomes an afterthought. +

+
+
+

Thus, if you care about machine readability, or human readability, or page weight, what you + should do is test.

+
+
+

Did you think we were going to talk about semantic HTML? Keep reading for that, but alas, + it’s no substitute for testing. Test manually. Test automatically. Test with + screenreaders, test with a keyboard, test on different browsers and hardware, run linters (while + coding and/or in CI). Read the markup — the markup being sent to browsers, not + the templates and code you write. Know exactly what any type of hypermedia client is doing in + any circumstance.

+
+
+

So where does HTML and the s-word come in?

+
+
+

Easy. Writing good, spec-compliant HTML lets browsers do a bunch of work for you. Furthermore, + even when they don’t, it makes it easier to write scripts that do. Fewer issues will be + found during testing and you can release faster. When issues do come up, you can often fix them + more easily by rewriting semantic HTML as opposed to heaping JavaScript and ARIA attributes over + everything.

+
+
+

Good HTML will not absolve you from doing your job, but it will make it easier.

+
+
+
+

3.2. The S word

+
+
+
+

Gretchen, stop trying to make fetch happen! It’s not going to happen!

+
+
+
+ — _Mean Girls_ (2004) +
+
+
+

In human language, a word can only have a certain meaning if some group of people know it to have + that meaning. + You could define your own words and use them, the aforementioned Ted Nelson and company really + liked to, but it’s difficult. + Providing a definition when you use it is not enough; + you also need an audience interested enough to read and remember that definition.

+
+
+

As this applies to computer languages too, any hypermedia format which lets documents define + their own elements is an infinite universe of “fetch”-es to make happen. + Thus, let us renounce the schematamania that plagues many hypermedia discussions. + Instead, when we talk about semantics, we refer to the simple act of using elements in + accordance with their agreed-upon meaning.

+
+
+

Instead of being extensible through schemas or namespaces, or whatever DTDs are, HTML is extended + in two ways:

+
+
+
    +
  • +

    Ad-hoc extensibility. HTML is a fault-tolerant language, choosing to + ignore things it doesn’t recognize instead of throwing errors. + This means you can use unspecified attributes. + htmx relies on this heavily. + If extensions see common acceptance, they might even be incorporated into + specifications! (We can dream.)

    +
  • +
  • +

    Specified extension points. Things like classes, + <meta> tags, custom elements and data- attributes are + made for custom data. + They can be used to build meta-languages embedded in HTML.

    +
  • +
+
+
+

This might seem like a downgrade, and an anxiety-inducing one at that. + Think of the name collisions! + Indeed, it has some significant compromises, but it also correctly acknowledges that defining + custom semantics without prior agreement between all parties is a fiction. + A flexible format --not an infinity of namespaces with URLs pointing to nothing --is + “software design on the scale of decades”.

+
+
+

Let’s be real, after all --out of all the sites using “Open Graph” tags, how + many use the appropriate prefix attribute? How many of their developers even know + the prefix attribute exists?

+
+
+
+

3.3. HTML is for humans first

+
+

An HTML file is not a program that produces a human-readable document. + It is the document.

+
+
+

Treat HTML semantics not as a technical specification, but as something close to a style + guide. + The meanings of HTML elements and attributes + (besides a few like <meta> and <script>) + are derived from conventions of human communication: + intractably fuzzy, and culturally dependent. + They have quite specific meanings in the spec, + but these meanings can be used sarcastically, hyperbolically, …​

+
+
+

The relationship between the content and the markup means good HTML is actually quite expensive. + If a site has a developer/author split, authors will need to work closely with developers or + know HTML themselves to produce markup that reflects their intended meaning. + This is rarely feasible, except for sites written, designed and coded by one person. + Furthermore, for internationalized sites, content in different languages being injected into the + same elements can degrade markup quality as stylistic conventions differ between languages. + Dishearteningly, but understandably, it’s an expense few organizations can spare.

+
+
+

Thus, we don’t demand that every site makes the "most semantic" HTML. + What’s most important is to avoid wrong HTML---it’s better to fall back on + a more generic element than be precisely incorrect. + Sometimes, div is fine.

+
+
+

If you have the resources, however, putting more care in your HTML will produce a more polished + site. + Much like style guides, well-written semantic HTML gives prestige to a document, even when few + people notice it. + It can also make your HTML easier to maintain.

+
+
+

Good HTML is also good for non-visual usage. + When working on accessibility, you may find yourself trying and failing to please screen + readers. + Especially if ARIA is involved, it can be extremely frustrating to do what seems like the right + thing and have assistive tools not do what you expect. + What helps with this frustration is to stop treating hypermedia exchanges as machine-to-machine + communication. + When browsers misbehave, we complain, but most of us don’t just down tools and concede: + "I did as the spec told me; it’s not my fault that the website is broken, my work here is + done." + We get in there and fix it. + So, instead of banging your head against a wall, focus on people, not the tools they use.

+
+
+

Don’t write HTML for browsers.
+ Don’t write HTML for assistive tools.
+ Don’t write HTML for validators.
+ HTML is not for them.
+ HTML is for humans.

+
+ +
+
+

3.4. Using the spec

+
+

While the big spec document with all the algorithms is probably better for smugly linking to + people in chatrooms, + don’t miss out on the developer-friendly version at https://html.spec.whatwg.org/dev/. +

+
+
+

For readers with better things to do, section 4 features a list of all tags in HTML. + It includes what tags mean, where they can occur, and what they are allowed to contain. + It even tells you when you’re allowed to leave out closing tags!

+
+
+
+
<!doctype html>
 This is a valid HTML document.
-
-
-
-

Revisit the “HTML5 elements”

-
-
-The beginning of wisdom is to call things by their right names. -
-
-— Confucius -
-
-
-

A set of elements introduced with HTML5 have become a symbol of semantic markup:

-
-
-
    -
  • -

    section

    -
  • -
  • -

    article

    -
  • -
  • -

    nav

    -
  • -
  • -

    header

    -
  • -
  • -

    footer

    -
  • -
-
-
-

Don’t write HTML with the mindset of -“The more of these elements there are, the more semantic it is.” -Otherwise, the results might look somewhat like this:

-
-
-
HTMHell, #10 <section> is no replacement for <div>, https://www.htmhell.dev/10-section-is-no-replacement-for-div/
-
-
<section id="page-top">
+                    
+
+
+

Revisit the “HTML5 elements”

+
+
+ The beginning of wisdom is to call things by their right names. +
+
+ — Confucius +
+
+
+

A set of elements introduced with HTML5 have become a symbol of semantic markup:

+
+
+
    +
  • +

    section

    +
  • +
  • +

    article

    +
  • +
  • +

    nav

    +
  • +
  • +

    header

    +
  • +
  • +

    footer

    +
  • +
+
+
+

Don’t write HTML with the mindset of + “The more of these elements there are, the more semantic it is.” + Otherwise, the results might look somewhat like this:

+
+
+
HTMHell, #10 <section> is no replacement for + <div>, https://www.htmhell.dev/10-section-is-no-replacement-for-div/
+
+
<section id="page-top">
   <section data-section-id="page-top" style="display: none;"></section>
 </section>
 <main>
@@ -2897,433 +6512,534 @@ Otherwise, the results might look somewhat like this:

</header> </section> </main>
-
-
-
-

Sometimes, <div> really is fine.

-
-
-

However, while you shouldn’t abuse advanced HTML, you shouldn’t restrict yourself either. -Instead, learn the meaning of every tag and consider each another tool in your tool chest. -(With the 113 elements currently defined in the spec, it’s more of a tool shed).

-
-
-
-
-

3.5. HTML tips and tricks

-
-

Label your inputs, one way or another

-
-

<input> elements always need to be labelled. However, they don’t necessarily need a visible <label> element. There are other ways to label:

-
-
-
    -
  • -

    Use a label, but hide it visually with CSS: Use the visually hidden utility for this.

    -
  • -
  • -

    Use aria-label:

    -
    -
    Search form using aria-label
    -
    -
    <form>
    +                        
    +
    +
    +

    Sometimes, <div> really is fine.

    +
    +
    +

    However, while you shouldn’t abuse advanced HTML, you shouldn’t restrict yourself + either. + Instead, learn the meaning of every tag and consider each another tool in your tool chest. + (With the 113 elements currently defined in the spec, it’s more of a tool shed).

    +
    +
+
+
+

3.5. HTML tips and tricks

+
+

Label your inputs, one way or another

+
+

<input> elements always need to be labelled. However, they don’t + necessarily need a visible <label> element. There are other ways to + label:

+
+
+
    +
  • +

    Use a label, but hide it visually with CSS: Use the visually hidden + utility for this.

    +
  • +
  • +

    Use aria-label:

    +
    +
    Search form using aria-label
    +
    +
    <form>
       <input type="search" aria-label="Search for...">
       <button>Search</button>
     </form>
    -
    -
    -
  • -
  • -

    Use aria-labelledby: -This can be a good option for inputs where a nearby element provides context.

    -
    -
    Search form using aria-labelledby
    -
    -
    <form>
    +                                    
    +
    +
  • +
  • +

    Use aria-labelledby: + This can be a good option for inputs where a nearby element provides context.

    +
    +
    Search form using aria-labelledby
    +
    +
    <form>
       <input type="search" aria-labelledby="search-button">
       <button id="search-button">Search</button>
     </form>
    -
    -
    -
  • -
-
-
-
-

Don’t use <figcaption> for alt-text

-
-

Use the alt attribute instead! Figure captions shouldn’t regurgitate the information in an image. Instead, they should be used to give context or provide metadata such as source or date. The alt attribute, on the other hand, should be a substitute for the image, describing the relevant aspects of what is depicted for the benefit of people who can’t view it.

-
-
-

Captions might be misused for alt text when the author wants the alt text to be visible as text. To achieve this, some social media platforms use an “ALT” button that opens the alt text in a popup window. The main drawback of this approach is that you need to implement a popup window. You could also use a <p> after the image with aria-hidden on it.

-
- -
-
- -
-

Wherever possible, the text of a link should describe what the link points to without much context needed. Of course, you might need to alter it to fit a sentence structure, but you should avoid links that don’t give information other than “this is a link”.

-
-
- - - - - - - - - -
-Don’t - -

For user records, [click here]

-
-Do - -

[User records]

-
-
-
-
-

ARIA is easy --as long as you stick to the basics

+
+
+ + +
+
+
+

Don’t use <figcaption> for alt-text

+
+

Use the alt attribute instead! Figure captions shouldn’t regurgitate the + information in an image. Instead, they should be used to give context or provide metadata + such as source or date. The alt attribute, on the other hand, should be a + substitute for the image, describing the relevant aspects of what is depicted for the + benefit of people who can’t view it.

+
+
+

Captions might be misused for alt text when the author wants the alt text to be visible as + text. To achieve this, some social media platforms use an “ALT” button that + opens the alt text in a popup window. The main drawback of this approach is that you need to + implement a popup window. You could also use a <p> after the image with + aria-hidden on it.

+
+ +
+
+ +
+

Wherever possible, the text of a link should describe what the link points to without much + context needed. Of course, you might need to alter it to fit a sentence structure, but you + should avoid links that don’t give information other than “this is a + link”.

+
+
+ + + + + + + + + +
+ Don’t + +

For user records, [click here]

+
+ Do + +

[User records]

+
+
+
+
+

ARIA is easy --as long as you stick to the + basics

-
-
-
-

3.6. Extending HTML

-
-

Data attributes

+
+
+
+

3.6. Extending HTML

+
+

Data attributes

-
-
-

Microformats

- -
-
-

Custom elements?

+
+
+

Microformats

+ +
+
+

Custom elements?

-
-
-
-
-
-

4. A Web 1.0 Application

-
-
-

To start our journey into Hypermedia-Driven Applications, we are going to create a simple contact management web -application called Contact.app. We will start with a basic, “Web 1.0-style” Multi-Page Application (MPA), in the grand -CRUD (Create, Read, Update, Delete) tradition. It will not be the best contact management application in the world. But -that’s OK because it will be simple (a great virtue of web 1.0 applications!) and it will do its job.

-
-
-

This application will also be easy to incrementally improve in the coming chapter by utilizing the hypermedia-oriented -library htmx.

-
-
-

By the time we are finished building and enhancing the application, over the next few chapters, it will have some very -slick features that most developers today would assume requires the use of a heavy JavaScript framework.

-
-
-

4.1. Picking A “Web Stack” To Use

-
-

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.

-
-
-

For this book we are going to use the following stack:

-
-
-
    -
  • -

    We will use Python as our programming language.

    -
  • -
  • -

    We will use Flask as our web framework, allowing us to connect HTTP requests to python logic.

    -
  • -
  • -

    We will use Jinja2 for our server-side templating language, allowing us to render HTML responses using a familiar -and intuitive syntax.

    -
  • -
-
-
-

Why did we pick this particular stack?

-
-
-

First, Python is the most popular programming language in the world, as of this writing, according to the -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 -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.

-
-
-

Jinja2 templates were picked 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.

-
-
-

With this stack we will be rendering HTML on the server-side to return to clients, rather than producing JSON. This -is the traditional approach to building web applications, but, with the rise of SPAs, is not as widely used a technique -as it once was. Today, as people are rediscovering this approach to building web applications, the term “Server-Side -Rendering” or SSR is emerging as the way that people talk about this style of templating. This is in contrast with -“Client-Side Rendering”, that is, rendering templates in the browser with data retrieved in JSON form from the server, -as is common in SPA libraries.

-
-
-

In Contact.app we will intentionally keep things as simple as possible to maximize the teaching value of our code: it -won’t be perfectly factored code, and it certainly won’t be the most beautiful web application ever built, but it will -be easy to follow for readers even if they have little Python experience, and it should be easy to translate the application -and the techniques demonstrated into your preferred programming language and web framework.

-
-
-
-

4.2. Python

-
-

Since this book is intended to teach how hypermedia can be used effectively, we aren’t going to do deep dives into -the various technologies we use around that hypermedia. This has some obvious drawbacks: if you aren’t comfortable -with Python, for example, some example python code in the book may be a bit confusing or mysterious at first.

-
-
-

If you feel like you need a quick introduction to the language before diving into the code, we recommend the following -books/websites:

-
-
- -
-
-

That being said, we think most web developers, even developers who are unfamiliar with Python, should be able to follow -along in the code. Most of the authors hadn’t written very much Python before writing this book, and we got the hang of -it pretty quickly.

-
-
-
-

4.3. Introducing Flask: Our First Route

-
-

Flask is a very simple but flexible web framework for Python. Just like this book isn’t a Python book, it isn’t a Flask book -either, so we will only go into as much detail about it as is necessary to show off hypermedia concepts. However, unlike -Python, which is similar in many ways to other programming languages, Flask might be a bit different than web frameworks -you are familiar with, so we will need to do a bit more of an introduction to it in order to prepare you for the coming -chapters.

-
-
-

Thankfully, Flask is simple enough that most web developers shouldn’t have a problem following along, so let’s go over -the core ideas.

-
-
-

A Flask application consists of a series of routes tied to functions that execute when an HTTP request to a given path is -made. It uses a Python feature called “decorators” to declare the route that will be handled, which is then followed by -a function to handle request to that route. We will often use the term “handler” to refer to the functions associated -with a route.

-
-
-

Let’s create our first route definition, a simple “Hello Flask” route. In the following python code you will see the -@app symbol. This is the flask decorator that allows us to set up our routes. Don’t worry too much about -how decorators work in Python, just know that this feature allows us to map a given path to a particular function -(i.e. handler). The flask application, when started, will take HTTP requests and look up the matching handler and -invoke it.

-
-
-

Here is what the code looks like:

-
-
-
A Simple “Hello World” Route
-
-
@app.route("/") (1)
+                
+
+
+
+
+

4. A Web 1.0 Application

+
+
+

To start our journey into Hypermedia-Driven Applications, we are going to create a simple contact + management web + application called Contact.app. We will start with a basic, “Web 1.0-style” Multi-Page + Application (MPA), in the grand + CRUD (Create, Read, Update, Delete) tradition. It will not be the best contact management + application in the world. But + that’s OK because it will be simple (a great virtue of web 1.0 applications!) and it will do + its job.

+
+
+

This application will also be easy to incrementally improve in the coming chapter by utilizing the + hypermedia-oriented + library htmx.

+
+
+

By the time we are finished building and enhancing the application, over the next few chapters, it + will have some very + slick features that most developers today would assume requires the use of a heavy JavaScript + framework.

+
+
+

4.1. Picking A “Web Stack” To Use

+
+

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.

+
+
+

For this book we are going to use the following stack:

+
+
+
    +
  • +

    We will use Python as our programming language.

    +
  • +
  • +

    We will use Flask as our web + framework, allowing us to connect HTTP requests to python logic.

    +
  • +
  • +

    We will use Jinja2 for our server-side + templating language, allowing us to render HTML responses using a familiar + and intuitive syntax.

    +
  • +
+
+
+

Why did we pick this particular stack?

+
+
+

First, Python is the most popular programming language in the world, as of this writing, + according to the + 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 + 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.

+
+
+

Jinja2 templates were picked 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.

+
+
+

With this stack we will be rendering HTML on the server-side to return to clients, + rather than producing JSON. This + is the traditional approach to building web applications, but, with the rise of SPAs, is not as + widely used a technique + as it once was. Today, as people are rediscovering this approach to building web applications, + the term “Server-Side + Rendering” or SSR is emerging as the way that people talk about this style of templating. + This is in contrast with + “Client-Side Rendering”, that is, rendering templates in the browser with data + retrieved in JSON form from the server, + as is common in SPA libraries.

+
+
+

In Contact.app we will intentionally keep things as simple as possible to maximize the teaching + value of our code: it + won’t be perfectly factored code, and it certainly won’t be the most beautiful web + application ever built, but it will + be easy to follow for readers even if they have little Python experience, and it should be easy + to translate the application + and the techniques demonstrated into your preferred programming language and web framework.

+
+
+
+

4.2. Python

+
+

Since this book is intended to teach how hypermedia can be used effectively, we aren’t + going to do deep dives into + the various technologies we use around that hypermedia. This has some obvious + drawbacks: if you aren’t comfortable + with Python, for example, some example python code in the book may be a bit confusing or + mysterious at first.

+
+
+

If you feel like you need a quick introduction to the language before diving into the code, we + recommend the following + books/websites:

+
+
+ +
+
+

That being said, we think most web developers, even developers who are unfamiliar with Python, + should be able to follow + along in the code. Most of the authors hadn’t written very much Python before writing this + book, and we got the hang of + it pretty quickly.

+
+
+
+

4.3. Introducing Flask: Our First Route

+
+

Flask is a very simple but flexible web framework for Python. Just like this book isn’t a + Python book, it isn’t a Flask book + either, so we will only go into as much detail about it as is necessary to show off hypermedia + concepts. However, unlike + Python, which is similar in many ways to other programming languages, Flask might be a bit + different than web frameworks + you are familiar with, so we will need to do a bit more of an introduction to it in order to + prepare you for the coming + chapters.

+
+
+

Thankfully, Flask is simple enough that most web developers shouldn’t have a problem + following along, so let’s go over + the core ideas.

+
+
+

A Flask application consists of a series of routes tied to functions that execute when + an HTTP request to a given path is + made. It uses a Python feature called “decorators” to declare the route that will be + handled, which is then followed by + a function to handle request to that route. We will often use the term “handler” to + refer to the functions associated + with a route.

+
+
+

Let’s create our first route definition, a simple “Hello Flask” route. In the + following python code you will see the + @app symbol. This is the flask decorator that allows us to set up our routes. + Don’t worry too much about + how decorators work in Python, just know that this feature allows us to map a given + path to a particular function + (i.e. handler). The flask application, when started, will take HTTP requests and look up the + matching handler and + invoke it. +

+
+
+

Here is what the code looks like:

+
+
+
A Simple “Hello World” Route
+
+
@app.route("/") (1)
 def index(): (2)
     return "Hello World!" (3)
-
-
-
-
    -
  1. -

    Establishes we are mapping the / path as a route

    -
  2. -
  3. -

    The next method is the handler for that route

    -
  4. -
  5. -

    Returns the string “Hello World!” to the client

    -
  6. -
-
-
-

The route() method on the Flask decorator takes an argument: the path you wish the route to handle. Here we -pass in the root or / path, as a string, to handle requests to the root path.

-
-
-

This route declaration is then followed by a simple function definition, index(). In Python, decorators invoked in -this manner apply to the function immediately following them. Therefore, this function becomes the “handler” for that -route, and will be executed when an HTTP request to the given path is made.

-
-
-

Note that the name of the function doesn’t matter, we can call it whatever we’d like so long as it is unique. In this -case we chose index() because that fits with the route we are handling: the root “index” of the web -application.

-
-
-

So we have the index() function immediately following our route definition for the root, and this will become the -handler for the root URL in our web application.

-
-
-

The handler in this case is dead simple, it just returns a string, “Hello Flask!”, to the client. This isn’t even -hypermedia yet, but, nonetheless, a browser will render it just fine:

-
-
-
-figure 2 1 hello world -
-
Figure 1. Hello Flask!
-
-
-

Great, there’s our first step into Flask, showing the core technique we are going to use to respond to HTTP requests: -routes mapped to handlers.

-
-
-

For Contact.app, rather than rendering “Hello Flask!” at the root path, we are going to do something a little fancy: -we are going to redirect to another path, the /contacts path. Redirects are a feature of HTTP that allow you to -redirect a client to another location with an HTTP response.

-
-
-

We are going to display a list of contacts as our root page, and, arguably, redirecting to the /contacts path to -display this information is a bit more consistent with notion of resources with REST. This is a judgement call on our -part, and not something we feel is too important, but it makes sense in terms of routes we will set up later in the -application.

-
-
-

To change our “Hello World” route to a redirect, we only need to change one line of code:

-
-
-
Changing “Hello World” to a Redirect
-
-
@app.route("/")
+                    
+
+
+
    +
  1. +

    Establishes we are mapping the / path as a route

    +
  2. +
  3. +

    The next method is the handler for that route

    +
  4. +
  5. +

    Returns the string “Hello World!” to the client

    +
  6. +
+
+
+

The route() method on the Flask decorator takes an argument: the path you wish the + route to handle. Here we + pass in the root or / path, as a string, to handle requests to the root path.

+
+
+

This route declaration is then followed by a simple function definition, index(). In + Python, decorators invoked in + this manner apply to the function immediately following them. Therefore, this function becomes + the “handler” for that + route, and will be executed when an HTTP request to the given path is made.

+
+
+

Note that the name of the function doesn’t matter, we can call it whatever we’d like + so long as it is unique. In this + case we chose index() because that fits with the route we are handling: the root + “index” of the web + application.

+
+
+

So we have the index() function immediately following our route definition for the + root, and this will become the + handler for the root URL in our web application.

+
+
+

The handler in this case is dead simple, it just returns a string, “Hello Flask!”, to + the client. This isn’t even + hypermedia yet, but, nonetheless, a browser will render it just fine:

+
+
+
+ figure 2 1 hello world +
+
Figure 1. Hello Flask!
+
+
+

Great, there’s our first step into Flask, showing the core technique we are going to use to + respond to HTTP requests: + routes mapped to handlers.

+
+
+

For Contact.app, rather than rendering “Hello Flask!” at the root path, we are going + to do something a little fancy: + we are going to redirect to another path, the /contacts path. Redirects are a + feature of HTTP that allow you to + redirect a client to another location with an HTTP response.

+
+
+

We are going to display a list of contacts as our root page, and, arguably, redirecting to the + /contacts path to + display this information is a bit more consistent with notion of resources with REST. This is a + judgement call on our + part, and not something we feel is too important, but it makes sense in terms of routes we will + set up later in the + application.

+
+
+

To change our “Hello World” route to a redirect, we only need to change one line of + code:

+
+
+
Changing “Hello World” to a Redirect
+
+
@app.route("/")
 def index():
     return redirect("/contacts") (1)
-
-
-
-
    -
  1. -

    Update to a call to redirect()

    -
  2. -
-
-
-

Now the index() function simply returns the result of calling the Flask-supplied redirect() function with the path -we with to redirect the user to. In this case the path is /contacts, and we pass this path in as a string argument. -Now, if you navigate to the root path, /, our Flask application will forward you on to the /contacts path.

-
-
-
-

4.4. Contact.app Functionality

-
-

OK, now that we have our feet under us with respect to defining routes, let’s get down to specifying and then implementing -our web application.

-
-
-

What will Contact.app do?

-
-
-

Initially, it will provide the following functionality:

-
-
-
    -
  • -

    Provide a list of contacts, including first name, last name, phone and email address

    -
  • -
  • -

    Provide the ability to search the list of contacts

    -
  • -
  • -

    Provide the ability to add a new contact to the list

    -
  • -
  • -

    Provide the ability to view the details of a contact on the list

    -
  • -
  • -

    Provide the ability to edit the details of a contact on the list

    -
  • -
  • -

    Provide the ability to delete a contact from the list

    -
  • -
-
-
-

So, as you can see, Contact.app is a fairly basic CRUD application, the sort of application that is perfect for an old-school -web 1.0 approach.

-
-
-

Note that the source code of Contact.app is available on GitHub.

-
-
-

Showing A Searchable List Of Contacts

-
-

Let’s look at our first real bit of functionality: the ability to show all the contacts in our system in a list (really, -in a table).

-
-
-

This functionality is going to be found at the /contacts path, which is the path our previous route is redirecting to.

-
-
-

We will use Flask to route the /contacts path to a handler function, contacts(). This function is going to do one of -two things:

-
-
-
    -
  • -

    If there is a search term found in the request, it will filter down to only contacts matching that term

    -
  • -
  • -

    If not, it will simply list all contacts

    -
  • -
-
-
-

This is a common approach in web 1.0 style applications: the same URL that displays all instances of some resource -also serves as the search results page for those resources. Taking this approach makes it easy to reuse the list -display that is common to both types of request.

-
-
-

Here is what the code looks like for this handler:

-
-
-
Server Side Search
-
-
@app.route("/contacts")
+                    
+
+
+
    +
  1. +

    Update to a call to redirect()

    +
  2. +
+
+
+

Now the index() function simply returns the result of calling the Flask-supplied + redirect() function with the path + we with to redirect the user to. In this case the path is /contacts, and we pass + this path in as a string argument. + Now, if you navigate to the root path, /, our Flask application will forward you on + to the /contacts path.

+
+
+
+

4.4. Contact.app Functionality

+
+

OK, now that we have our feet under us with respect to defining routes, let’s get down to + specifying and then implementing + our web application.

+
+
+

What will Contact.app do?

+
+
+

Initially, it will provide the following functionality:

+
+
+
    +
  • +

    Provide a list of contacts, including first name, last name, phone and email address

    +
  • +
  • +

    Provide the ability to search the list of contacts

    +
  • +
  • +

    Provide the ability to add a new contact to the list

    +
  • +
  • +

    Provide the ability to view the details of a contact on the list

    +
  • +
  • +

    Provide the ability to edit the details of a contact on the list

    +
  • +
  • +

    Provide the ability to delete a contact from the list

    +
  • +
+
+
+

So, as you can see, Contact.app is a fairly basic CRUD application, the sort of application that + is perfect for an old-school + web 1.0 approach.

+
+
+

Note that the source code of Contact.app is available on GitHub.

+
+
+

Showing A Searchable List Of Contacts

+
+

Let’s look at our first real bit of functionality: the ability to show all the contacts + in our system in a list (really, + in a table).

+
+
+

This functionality is going to be found at the /contacts path, which is the path + our previous route is redirecting to.

+
+
+

We will use Flask to route the /contacts path to a handler function, + contacts(). This function is going to do one of + two things:

+
+
+
    +
  • +

    If there is a search term found in the request, it will filter down to only contacts + matching that term

    +
  • +
  • +

    If not, it will simply list all contacts

    +
  • +
+
+
+

This is a common approach in web 1.0 style applications: the same URL that displays all + instances of some resource + also serves as the search results page for those resources. Taking this approach makes it + easy to reuse the list + display that is common to both types of request.

+
+
+

Here is what the code looks like for this handler:

+
+
+
Server Side Search
+
+
@app.route("/contacts")
 def contacts():
     search = request.args.get("q") (1)
     if search is not None:
@@ -3331,115 +7047,145 @@ def contacts():
     else:
         contacts_set = Contact.all() (3)
     return render_template("index.html", contacts=contacts_set) (4)
-
-
-
-
    -
  1. -

    Look for the query parameter named q, which stands for “query”

    -
  2. -
  3. -

    If the parameter exists, call the Contact.search() function with it

    -
  4. -
  5. -

    If not, call the Contact.all() function

    -
  6. -
  7. -

    pass the result to the index.html template to render to the client

    -
  8. -
-
-
-

We see the same sort of routing code we saw in our first example, but we have a more elaborate handler function. -First, we check to see if a search query parameter named q is part of the request.

-
-
-
-
Query Strings
-
-

A “query string” is part of the URL specification, and you are probably familiar with this term, but -for those who are not, let’s review what it is. Here is an example URL with a query string in it: -https://example.com/contacts?q=joe. The query string is everything after the ? and, you can see, it has a -name-value pair format. In this URL, the query parameter q is set to the string value joe. In plain HTML, a -query string can be included in a request either by being hardcoded in an anchor tag or, more dynamically, by -using a form tag with a GET request.

-
-
-
-
-

To return to our Flask route, if a query parameter named q is found, we call out to the search() method on a -Contact model object to do the actual contact search and return all the matching contacts.

-
-
-

If the query parameter is not found, we simply get all contacts by invoking the all() method on the Contact object.

-
-
-

Finally, we then render a template, index.html that displays the given contacts, passing in the results of whichever -of these two functions we ended up calling.

-
- -
-
The List & Search Templates
-
-

Now that we have our handler logic written, we need to take a look at the templates that we are going to use to render -HTML in our response to the client. At a high level, our HTML response needs to have the following elements:

-
-
-
    -
  • -

    A list of any matching or all contacts

    -
  • -
  • -

    A search box that a user may type a search term into and then submit for searches

    -
  • -
  • -

    A bit of surrounding “chrome”: a header and footer for the website that will be the same regardless of the page you -are on

    -
  • -
-
-
-

We are using the Jinja2 templating language, which has the following features:

-
-
-
    -
  • -

    We can use double-curly braces, {{ }}, to embed expression values in the template.

    -
  • -
  • -

    we can use curly-percents, {% %}, for directives, like iteration or including other content.

    -
  • -
-
-
-

Beyond this basic syntax, Jinja2 is very similar to other templating languages used to generate content, and should -be easy to follow for most web developers.

-
-
-

Let’s look at the first few lines of code in the index.html template:

-
-
-
Start of index.html
-
-
{% extends 'layout.html' %} (1)
+                        
+
+
+
    +
  1. +

    Look for the query parameter named q, which stands for + “query”

    +
  2. +
  3. +

    If the parameter exists, call the Contact.search() function with it

    +
  4. +
  5. +

    If not, call the Contact.all() function

    +
  6. +
  7. +

    pass the result to the index.html template to render to the client

    +
  8. +
+
+
+

We see the same sort of routing code we saw in our first example, but we have a more + elaborate handler function. + First, we check to see if a search query parameter named q is part of the + request.

+
+
+
+
Query Strings
+
+

A “query string” is part of the URL specification, and you are probably + familiar with this term, but + for those who are not, let’s review what it is. Here is an example URL with a + query string in it: + https://example.com/contacts?q=joe. + The query string is everything after the ? and, you can see, it has a + name-value pair format. In this URL, the query parameter q is set to + the string value joe. In plain HTML, a + query string can be included in a request either by being hardcoded in an anchor tag + or, more dynamically, by + using a form tag with a GET request. +

+
+
+
+
+

To return to our Flask route, if a query parameter named q is found, we call out + to the search() method on a + Contact model object to do the actual contact search and return all the + matching contacts. +

+
+
+

If the query parameter is not found, we simply get all contacts by invoking the + all() method on the Contact object.

+
+
+

Finally, we then render a template, index.html that displays the given contacts, + passing in the results of whichever + of these two functions we ended up calling.

+
+ +
+
The List & Search Templates
+
+

Now that we have our handler logic written, we need to take a look at the templates that + we are going to use to render + HTML in our response to the client. At a high level, our HTML response needs to have the + following elements:

+
+
+
    +
  • +

    A list of any matching or all contacts

    +
  • +
  • +

    A search box that a user may type a search term into and then submit for searches +

    +
  • +
  • +

    A bit of surrounding “chrome”: a header and footer for the website + that will be the same regardless of the page you + are on

    +
  • +
+
+
+

We are using the Jinja2 templating language, which has the following features:

+
+
+
    +
  • +

    We can use double-curly braces, {{ }}, to embed expression values in + the template.

    +
  • +
  • +

    we can use curly-percents, {% %}, for directives, like iteration or + including other content.

    +
  • +
+
+
+

Beyond this basic syntax, Jinja2 is very similar to other templating languages used to + generate content, and should + be easy to follow for most web developers.

+
+
+

Let’s look at the first few lines of code in the index.html template: +

+
+
+
Start of index.html
+
+
{% extends 'layout.html' %} (1)
 
 {% block content %} (2)
 
@@ -3448,63 +7194,80 @@ be easy to follow for most web developers.

<input id="search" type="search" name="q" value="{{ request.args.get('q') or '' }}"/> (4) <input type="submit" value="Search"/> </form>
-
-
-
-
    -
  1. -

    Set the layout template for this template

    -
  2. -
  3. -

    Delimit the content to be inserted into the layout

    -
  4. -
  5. -

    Create a search form that will issue an HTTP GET to /contacts

    -
  6. -
  7. -

    Create an input that a query can be typed into to search contacts

    -
  8. -
-
-
-

The first line of code references a base template, layout.html, with the extends directive. This layout -template provides the layout for the page (again, sometimes called “the chrome”): it wraps the template content in an -<html> tag, imports any necessary CSS and JavaScript in a <head> element, places a <body> tag around the main -content and so forth. All the common content that wrapped around the “normal” content for the entire application -is located in this file.

-
-
-

The next line of code declares the content section of this template. This content block is used by the layout.html -template to inject the content of index.html within its HTML.

-
-
-

Next we have our first bit of actual HTML, rather than just Jinja directives. We have a simple HTML form that allows -you to search contacts by issuing a GET request to the /contacts path. The form itself contains a label and -an input with the name “q”. This input’s value will be submitted with the GET request to the /contacts path, -as a query string (since this is a GET request.)

-
-
-

Note that the value of this input is set to the Jinja expression {{ request.args.get('q') or '' }}. This expression -is evaluated by Jinja and will insert the request value of “q” as the input’s value, if it exists. This will “preserve” -the search value when a user does a search, so that when the results of a search are rendered the text input contains -the term that was searched for. This makes for a better user experience since the user can see exactly what the -current results match, rather than having a blank text box at the top of the screen.

-
-
-

Finally, we have a submit-type input. This will render as a button and, when it is clicked on, it will trigger the -form to issue an HTTP request.

-
-
-

This search UI forms the top of our contact page. Following it is a table of contacts, either all contacts or the -contacts that match the search, if a search was done.

-
-
-

Here is what the template code for the contact table looks like:

-
-
-
The Contacts Table
-
-
    <table>
+                            
+
+
+
    +
  1. +

    Set the layout template for this template

    +
  2. +
  3. +

    Delimit the content to be inserted into the layout

    +
  4. +
  5. +

    Create a search form that will issue an HTTP GET to + /contacts

    +
  6. +
  7. +

    Create an input that a query can be typed into to search contacts

    +
  8. +
+
+
+

The first line of code references a base template, layout.html, with the + extends directive. This layout + template provides the layout for the page (again, sometimes called “the + chrome”): it wraps the template content in an + <html> tag, imports any necessary CSS and JavaScript in a + <head> element, places a <body> tag around the + main + content and so forth. All the common content that wrapped around the + “normal” content for the entire application + is located in this file. +

+
+
+

The next line of code declares the content section of this template. This + content block is used by the layout.html + template to inject the content of index.html within its HTML.

+
+
+

Next we have our first bit of actual HTML, rather than just Jinja directives. We have a + simple HTML form that allows + you to search contacts by issuing a GET request to the + /contacts path. The form itself contains a label and + an input with the name “q”. This input’s value will be submitted with + the GET request to the /contacts path, + as a query string (since this is a GET request.)

+
+
+

Note that the value of this input is set to the Jinja expression + {{ request.args.get('q') or '' }}. This expression + is evaluated by Jinja and will insert the request value of “q” as the + input’s value, if it exists. This will “preserve” + the search value when a user does a search, so that when the results of a search are + rendered the text input contains + the term that was searched for. This makes for a better user experience since the user + can see exactly what the + current results match, rather than having a blank text box at the top of the screen.

+
+
+

Finally, we have a submit-type input. This will render as a button and, when it is + clicked on, it will trigger the + form to issue an HTTP request.

+
+
+

This search UI forms the top of our contact page. Following it is a table of contacts, + either all contacts or the + contacts that match the search, if a search was done.

+
+
+

Here is what the template code for the contact table looks like:

+
+
+
The Contacts Table
+
+
    <table>
         <thead>
         <tr>
             <th>First</th> <th>Last</th> <th>Phone</th> <th>Email</th> <th></th>(1)
@@ -3523,151 +7286,177 @@ contacts that match the search, if a search was done.

{% endfor %} </tbody> </table>
-
-
-
-
    -
  1. -

    Output some headers for our table

    -
  2. -
  3. -

    Iterate over the contacts that were passed in to the template

    -
  4. -
  5. -

    Output the values of the current contact, first name, last name, etc.

    -
  6. -
  7. -

    An "operations" column, with links to edit or view the contact details

    -
  8. -
-
-
-

This is the core of the page: we construct a table with appropriate headers matching the data we are going -to show for each contact. We iterate over the contacts that were passed into the template by the handler method using -the for loop directive in Jinja2. We then construct a series of rows, one for each contact, where we render the -first and last name, phone and email of the contact as table cells in the row.

-
-
-

Additionally, we have another table cell that includes two links:

-
-
-
    -
  • -

    A link to the "Edit" page for the contact, located at /contacts/{{ contact.id }}/edit (e.g. For the contact with -id 42, the edit link will point to /contacts/42/edit)

    -
  • -
  • -

    A link to the "View" page for the contact /contacts/{{ contact.id }} (using our previous contact example, the view -page would be at /contacts/42)

    -
  • -
-
-
-

Finally, we have a bit of end-matter: a link to add a new contact and a Jinja2 directive to end the content block:

-
-
-
The Add Contact Link
-
-
    <p>
+                            
+
+
+
    +
  1. +

    Output some headers for our table

    +
  2. +
  3. +

    Iterate over the contacts that were passed in to the template

    +
  4. +
  5. +

    Output the values of the current contact, first name, last name, etc.

    +
  6. +
  7. +

    An "operations" column, with links to edit or view the contact details

    +
  8. +
+
+
+

This is the core of the page: we construct a table with appropriate headers matching the + data we are going + to show for each contact. We iterate over the contacts that were passed into the + template by the handler method using + the for loop directive in Jinja2. We then construct a series of rows, one + for each contact, where we render the + first and last name, phone and email of the contact as table cells in the row.

+
+
+

Additionally, we have another table cell that includes two links:

+
+
+
    +
  • +

    A link to the "Edit" page for the contact, located at + /contacts/{{ contact.id }}/edit (e.g. For the contact with + id 42, the edit link will point to /contacts/42/edit)

    +
  • +
  • +

    A link to the "View" page for the contact /contacts/{{ contact.id }} + (using our previous contact example, the view + page would be at /contacts/42)

    +
  • +
+
+
+

Finally, we have a bit of end-matter: a link to add a new contact and a Jinja2 directive + to end the content block:

+
+
+
The Add Contact Link
+
+
    <p>
         <a href="/contacts/new">Add Contact</a> (1)
     </p>
 
 {% endblock %} (2)
-
-
-
-
    -
  1. -

    Link to the page that allows you to create a new contact

    -
  2. -
  3. -

    The closing element of the content block

    -
  4. -
-
-
-

And that’s our complete template. Using this simple server-side template, in combination with our handler method, we -can respond with an HTML representation of all the contacts requested. So far, so hypermedia.

-
-
-

Here is what the template looks like, rendered with a bit of contact information:

-
-
-
-figure 2 2 table etc -
-
Figure 2. Contact.app
-
-
-

Now, our application won’t win any design awards at this point, but notice that our template, when rendered, -provides all the functionality necessary to see all the contacts and search them, and also provides links to edit them, -view details of them or even create a new one.

-
-
-

And it does all this without the client (that is, the browser) knowing a thing about what contacts are or how to -work with them. Everything is encoded in the hypermedia. A web browser accessing this application just knows how to -issue HTTP requests and then render HTML, nothing more about the specifics of our applications end points or underlying -domain model.

-
-
-

As simple as our application is at this point, it is thoroughly RESTful.

-
-
-
-
-

Adding A New Contact

-
-

The next bit of functionality that we will add to our application is the ability to add new contacts. To do this, we -are going to need to handle that /contacts/new URL referenced in the “Add Contact” link above. Note that when a user -clicks on that link, the browser will issue a GET request to the /contacts/new URL.

-
-
-

All the other routes we have been looking at so far are using GET as well, but we are actually going to use two -different HTTP methods for this bit of functionality: an HTTP GET to render a form for adding a new contact, -and then an HTTP POST to the same path to actually create the contact, so we are going to be explicit about the -HTTP method we want to handle when we declare this route.

-
-
-

Here is the code:

-
-
-
The New Contact GET Route
-
-
@app.route("/contacts/new", methods=['GET']) (1)
+                            
+
+
+
    +
  1. +

    Link to the page that allows you to create a new contact

    +
  2. +
  3. +

    The closing element of the content block

    +
  4. +
+
+
+

And that’s our complete template. Using this simple server-side template, in + combination with our handler method, we + can respond with an HTML representation of all the contacts requested. So far, + so hypermedia.

+
+
+

Here is what the template looks like, rendered with a bit of contact information:

+
+
+
+ figure 2 2 table etc +
+
Figure 2. Contact.app
+
+
+

Now, our application won’t win any design awards at this point, but notice that our + template, when rendered, + provides all the functionality necessary to see all the contacts and search them, and + also provides links to edit them, + view details of them or even create a new one.

+
+
+

And it does all this without the client (that is, the browser) knowing a thing about what + contacts are or how to + work with them. Everything is encoded in the hypermedia. A web browser + accessing this application just knows how to + issue HTTP requests and then render HTML, nothing more about the specifics of our + applications end points or underlying + domain model.

+
+
+

As simple as our application is at this point, it is thoroughly RESTful.

+
+
+
+
+

Adding A New Contact

+
+

The next bit of functionality that we will add to our application is the ability to add new + contacts. To do this, we + are going to need to handle that /contacts/new URL referenced in the “Add + Contact” link above. Note that when a user + clicks on that link, the browser will issue a GET request to the + /contacts/new URL.

+
+
+

All the other routes we have been looking at so far are using GET as well, but + we are actually going to use two + different HTTP methods for this bit of functionality: an HTTP GET to render a + form for adding a new contact, + and then an HTTP POST to the same path to actually create the contact, + so we are going to be explicit about the + HTTP method we want to handle when we declare this route.

+
+
+

Here is the code:

+
+
+
The New Contact GET Route
+
+
@app.route("/contacts/new", methods=['GET']) (1)
 def contacts_new_get():
     return render_template("new.html", contact=Contact()) (2)
-
-
-
-
    -
  1. -

    Declare a route, explicitly handling GET requests to this path

    -
  2. -
  3. -

    Render the new.html template, passing in a new contact object

    -
  4. -
-
-
-

Simple enough. We just render a new.html template with a new Contact. (Contact() is how you construct a new instance -of the Contact class in Python, if you aren’t familiar with it.)

-
-
-

While the handler code for this route is very simple, the new.html template is more complicated. For the -remaining templates we are going to omit the layout directive and the content block declaration, but you -can assume they are the same unless we say otherwise. This will let us focus on the "meat" of the template.

-
-
-

If you are familiar with HTML you are probably expecting a form element here, and you will not be disappointed. We are -going to use the standard form hypermedia control for collecting contact information and submitting it to the server.

-
-
-

Here is what our HTML looks like:

-
-
-
The New Contact Form
-
-
<form action="/contacts/new" method="post"> (1)
+                        
+
+
+
    +
  1. +

    Declare a route, explicitly handling GET requests to this path

    +
  2. +
  3. +

    Render the new.html template, passing in a new contact object

    +
  4. +
+
+
+

Simple enough. We just render a new.html template with a new Contact. + (Contact() is how you construct a new instance + of the Contact class in Python, if you aren’t familiar with it.)

+
+
+

While the handler code for this route is very simple, the new.html template is + more complicated. For the + remaining templates we are going to omit the layout directive and the content block + declaration, but you + can assume they are the same unless we say otherwise. This will let us focus on the "meat" + of the template.

+
+
+

If you are familiar with HTML you are probably expecting a form element here, and you will + not be disappointed. We are + going to use the standard form hypermedia control for collecting contact information and + submitting it to the server.

+
+
+

Here is what our HTML looks like:

+
+
+
The New Contact Form
+
+
<form action="/contacts/new" method="post"> (1)
     <fieldset>
         <legend>Contact Values</legend>
         <p>
@@ -3675,41 +7464,47 @@ going to use the standard form hypermedia control for collecting contact informa
             <input name="email" id="email" type="email" placeholder="Email" value="{{ contact.email or '' }}"> (3)
             <span class="error">{{ contact.errors['email'] }}</span> (4)
         </p>
-
-
-
-
    -
  1. -

    A form that submits to the /contacts/new path, using an HTTP POST

    -
  2. -
  3. -

    A label for the first form input

    -
  4. -
  5. -

    the first form input, of type email

    -
  6. -
  7. -

    Any error messages associated with this field

    -
  8. -
-
-
-

In the first line of code we create a form that will submit back to the same path that we are handling: /contacts/new. -Rather than issuing an HTTP GET to this path, however, we will issue an HTTP POST to it. Using a POST in this manner -will signal to the server that we want to create a new Contact, rather than get a form.

-
-
-

We then have a label (always a good practice, as mentioned in the last chapter!) and an input that captures the email of -the contact being created. The name of the input is email and, when this form is submitted, the value of this input -will be submitted in the POST request, associated with the email key.

-
-
-

Next we have inputs for the other fields for contacts:

-
-
-
Inputs And Labels For The New Contact Form
-
-
        <p>
+                        
+
+
+
    +
  1. +

    A form that submits to the /contacts/new path, using an HTTP + POST

    +
  2. +
  3. +

    A label for the first form input

    +
  4. +
  5. +

    the first form input, of type email

    +
  6. +
  7. +

    Any error messages associated with this field

    +
  8. +
+
+
+

In the first line of code we create a form that will submit back to the same path + that we are handling: /contacts/new. + Rather than issuing an HTTP GET to this path, however, we will issue an HTTP + POST to it. Using a POST in this manner + will signal to the server that we want to create a new Contact, rather than get a form.

+
+
+

We then have a label (always a good practice, as mentioned in the last chapter!) and an input + that captures the email of + the contact being created. The name of the input is email and, when this form + is submitted, the value of this input + will be submitted in the POST request, associated with the email + key.

+
+
+

Next we have inputs for the other fields for contacts:

+
+
+
Inputs And Labels For The New Contact Form
+
+
        <p>
             <label for="first_name">First Name</label>
             <input name="first_name" id="first_name" type="text" placeholder="First Name" value="{{ contact.first or '' }}">
             <span class="error">{{ contact.errors['first'] }}</span>
@@ -3724,54 +7519,65 @@ will be submitted in the POST request, associated with the em
             <input name="phone" id="phone" type="text" placeholder="Phone" value="{{ contact.phone or '' }}">
             <span class="error">{{ contact.errors['phone'] }}</span>
         </p>
-
-
-
-

Finally, we have a button that will submit the form, the end of the form tag, and a link back to the main contacts table:

-
-
-
The Submit Button For The New Contact Form
-
-
        <button>Save</button>
+                        
+
+
+

Finally, we have a button that will submit the form, the end of the form tag, and a link back + to the main contacts table:

+
+
+
The Submit Button For The New Contact Form
+
+
        <button>Save</button>
     </fieldset>
 </form>
 
 <p>
     <a href="/contacts">Back</a>
 </p>
-
-
-
-

It is worth pointing out something that is easy to miss in this straight-forward example: we are seeing the flexibility -of hypermedia in action.

-
-
-

If we add a new field, remove a field, or change the logic around how fields are validated or work with one another, -this new state of affairs would be reflected in the new hypermedia representation given to users. A user would see the -updated new form, and be able to work with whatever new features is had, with no software update required.

-
-
-
Handling The Post to /contacts/new
-
-

The next step in our application is to handle the POST that this form makes to /contacts/new.

-
-
-

To do so, we need to add another route to our application that handles the /contacts/new path like our handler above, -but that handles an HTTP POST method instead of an HTTP GET. We will use the submitted form values to attempt to -create a new Contact.

-
-
-

If we are successful in creating a Contact, we will redirect the user to the list of contacts and show a success message. -If we aren’t successful, then we will render the new contact form again with whatever values the user entered and -render error messages about what issues need to be fixed so that the user can correct them.

-
-
-

Here is our new request handler:

-
-
-
The New Contact Controller Code
-
-
@app.route("/contacts/new", methods=['POST'])
+                        
+
+
+

It is worth pointing out something that is easy to miss in this straight-forward example: we + are seeing the flexibility + of hypermedia in action.

+
+
+

If we add a new field, remove a field, or change the logic around how fields are validated or + work with one another, + this new state of affairs would be reflected in the new hypermedia representation given to + users. A user would see the + updated new form, and be able to work with whatever new features is had, with no software + update required.

+
+
+
Handling The Post to /contacts/new
+
+

The next step in our application is to handle the POST that this form makes + to /contacts/new.

+
+
+

To do so, we need to add another route to our application that handles the + /contacts/new path like our handler above, + but that handles an HTTP POST method instead of an HTTP GET. + We will use the submitted form values to attempt to + create a new Contact.

+
+
+

If we are successful in creating a Contact, we will redirect the user to the list of + contacts and show a success message. + If we aren’t successful, then we will render the new contact form again with + whatever values the user entered and + render error messages about what issues need to be fixed so that the user can correct + them.

+
+
+

Here is our new request handler:

+
+
+
The New Contact Controller Code
+
+
@app.route("/contacts/new", methods=['POST'])
 def contacts_new():
     c = Contact(None, request.form['first_name'], request.form['last_name'], request.form['phone'],
                 request.form['email']) (1)
@@ -3780,181 +7586,226 @@ def contacts_new():
         return redirect("/contacts") (3)
     else:
         return render_template("new.html", contact=c) (4)
-
-
-
-
    -
  1. -

    We construct a new contact object with the values from the form

    -
  2. -
  3. -

    We try to save it

    -
  4. -
  5. -

    On success, “flash” a success message & redirect to the /contacts page

    -
  6. -
  7. -

    On failure, re-render the form, showing any errors to the user

    -
  8. -
-
-
-

The logic in this handler is a bit more complex than other methods we have seen, but it isn’t too bad. The first thing -we do is create a new Contact, again using the Contact() syntax in Python to construct the object. We pass in the values -that the user submitted in the form by using the request.form object, a feature provided by Flask.

-
-
-

This request.form allows us to access submitted form values in an easy and convenient way, by simply passing in the same -name associated with the various inputs.

-
-
-

We also pass in None as the first value to the Contact constructor. This is the “id” parameter, and by passing in -None we are signaling that it is a new contact, and needs to have an ID generated for it. (Again, we are not -going to dig deeply into the details of how this model object is implemented, our only concern is using it to generate -hypermedia responses.)

-
-
-

Next, we call the save() method on the Contact object. This method returns true if the save is successful, and false if -the save is unsuccessful (for example, a bad email was submitted by the user)

-
-
-

If we are able to save the contact (that is, there were no validation errors), we create a flash message indicating -success and redirect the browser back to the list page. A “flash” is a common feature in web frameworks that allows -you to store a message that will be available on the next request, typically in a cookie or in a session store.

-
-
-

Finally, if we are unable to save the contact, we re-render the new.html template with the contact. This will show the -same template as above, but the inputs will be filled in with the submitted values, and any errors associated with the -fields will be rendered to feedback to the user as to what validation failed.

-
- -
-

OK, so we have our server-side logic set up to save contacts. And, believe it or not, this is about as complicated as -our handler logic will get, even when we look at adding more sophisticated htmx-driven behaviors. Simplicity is a great -selling point for hypermedia!

-
-
-
-
-

Viewing The Details Of A Contact

-
-

The next piece of functionality we will implement is the detail page for a Contact. The user will navigate to this -page by clicking the “View” link in one of the rows in the list of contacts. This will take them to the path -/contact/<contact id> (e.g. /contacts/42).

-
-
-

Note that this is a common pattern in web development: Contacts are being treated as resources and the URLs around these -resources are organized in a coherent manner:

-
-
-
    -
  • -

    If you wish to view all contacts, you issue a GET to /contacts

    -
  • -
  • -

    If you wish to get a hypermedia representation allowing you to create a new contact, you issue a GET to /contacts/new

    -
  • -
  • -

    If you wish to view a specific contact (with, say, an id of 42), you issue a `GET to /contacts/42

    -
  • -
-
- -
-

Our handler logic for the detail route is going to be very simple: we just look the Contact up by id, which is embedded in -the path of the URL for the route. To extract this ID we are going to need to introduce a final bit of Flask -functionality: the ability to call out pieces of a path and have them automatically extracted and passed in to a -handler function.

-
-
-

Here is what the code looks like, just a few lines of simple Python:

-
-
-
-
@app.route("/contacts/<contact_id>") (1)
+                            
+
+
+
    +
  1. +

    We construct a new contact object with the values from the form

    +
  2. +
  3. +

    We try to save it

    +
  4. +
  5. +

    On success, “flash” a success message & redirect to the + /contacts page

    +
  6. +
  7. +

    On failure, re-render the form, showing any errors to the user

    +
  8. +
+
+
+

The logic in this handler is a bit more complex than other methods we have seen, but it + isn’t too bad. The first thing + we do is create a new Contact, again using the Contact() syntax in Python + to construct the object. We pass in the values + that the user submitted in the form by using the request.form object, a + feature provided by Flask.

+
+
+

This request.form allows us to access submitted form values in an easy and + convenient way, by simply passing in the same + name associated with the various inputs.

+
+
+

We also pass in None as the first value to the Contact + constructor. This is the “id” parameter, and by passing in + None we are signaling that it is a new contact, and needs to have an ID + generated for it. (Again, we are not + going to dig deeply into the details of how this model object is implemented, our only + concern is using it to generate + hypermedia responses.) +

+
+
+

Next, we call the save() method on the Contact object. This method returns + true if the save is successful, and false if + the save is unsuccessful (for example, a bad email was submitted by the user)

+
+
+

If we are able to save the contact (that is, there were no validation errors), we create + a flash message indicating + success and redirect the browser back to the list page. A “flash” is a + common feature in web frameworks that allows + you to store a message that will be available on the next request, typically in + a cookie or in a session store.

+
+
+

Finally, if we are unable to save the contact, we re-render the new.html + template with the contact. This will show the + same template as above, but the inputs will be filled in with the submitted values, and + any errors associated with the + fields will be rendered to feedback to the user as to what validation failed.

+
+ +
+

OK, so we have our server-side logic set up to save contacts. And, believe it or not, + this is about as complicated as + our handler logic will get, even when we look at adding more sophisticated htmx-driven + behaviors. Simplicity is a great + selling point for hypermedia!

+
+
+
+
+

Viewing The Details Of A Contact

+
+

The next piece of functionality we will implement is the detail page for a Contact. The user + will navigate to this + page by clicking the “View” link in one of the rows in the list of contacts. + This will take them to the path + /contact/<contact id> (e.g. /contacts/42). +

+
+
+

Note that this is a common pattern in web development: Contacts are being treated as + resources and the URLs around these + resources are organized in a coherent manner:

+
+
+
    +
  • +

    If you wish to view all contacts, you issue a GET to + /contacts

    +
  • +
  • +

    If you wish to get a hypermedia representation allowing you to create a new contact, + you issue a GET to /contacts/new

    +
  • +
  • +

    If you wish to view a specific contact (with, say, an id of + 42), you issue a `GET to /contacts/42

    +
  • +
+
+ +
+

Our handler logic for the detail route is going to be very simple: we just look the + Contact up by id, which is embedded in + the path of the URL for the route. To extract this ID we are going to need to introduce a + final bit of Flask + functionality: the ability to call out pieces of a path and have them automatically + extracted and passed in to a + handler function.

+
+
+

Here is what the code looks like, just a few lines of simple Python:

+
+
+
+
@app.route("/contacts/<contact_id>") (1)
 def contacts_view(contact_id=0): (2)
     contact = Contact.find(contact_id) (3)
     return render_template("show.html", contact=contact) (4)
-
-
-
-
    -
  1. -

    Map the path, with a path variable named contact_id

    -
  2. -
  3. -

    The handler takes the value of this path parameter

    -
  4. -
  5. -

    Look up the corresponding contact

    -
  6. -
  7. -

    Render the show.html template

    -
  8. -
-
-
-

You can see the syntax for extracting values from the path in the first line of code: you enclose the part of the -path you wish to extract in <> and give it a name. This component of the path will be extracted and then passed -into the handler function, via the parameter with the same name.

-
-
-

So, if you were to navigate to the path /contacts/42 then the value 42 would be passed into the contacts_view() -function for the value of contact_id.

-
-
-

Once we have the id of the contact we want to look up, we load it up using the find method on the Contact object. We -then pass this contact into the show.html template and render a response.

-
-
-
-

The Contact Detail Template

-
-

Our show.html template is relatively simple, just showing the same information as the table but in a slightly different -format (perhaps for printing). If we add functionality like “notes” to the application later on, however, this will give -us a good place to do so.

-
-
-

Again, we will omit the “chrome” of the template and focus on the meat:

-
-
-
The Contact Details Template
-
-
<h1>{{contact.first}} {{contact.last}}</h1>
+                        
+
+
+
    +
  1. +

    Map the path, with a path variable named contact_id

    +
  2. +
  3. +

    The handler takes the value of this path parameter

    +
  4. +
  5. +

    Look up the corresponding contact

    +
  6. +
  7. +

    Render the show.html template

    +
  8. +
+
+
+

You can see the syntax for extracting values from the path in the first line of code: you + enclose the part of the + path you wish to extract in <> and give it a name. This component of the + path will be extracted and then passed + into the handler function, via the parameter with the same name.

+
+
+

So, if you were to navigate to the path /contacts/42 then the value + 42 would be passed into the contacts_view() + function for the value of contact_id.

+
+
+

Once we have the id of the contact we want to look up, we load it up using the + find method on the Contact object. We + then pass this contact into the show.html template and render a response.

+
+
+
+

The Contact Detail Template

+
+

Our show.html template is relatively simple, just showing the same information + as the table but in a slightly different + format (perhaps for printing). If we add functionality like “notes” to the + application later on, however, this will give + us a good place to do so.

+
+
+

Again, we will omit the “chrome” of the template and focus on the meat:

+
+
+
The Contact Details Template
+
+
<h1>{{contact.first}} {{contact.last}}</h1>
 
 <div>
   <div>Phone: {{contact.phone}}</div>
@@ -3965,55 +7816,68 @@ us a good place to do so.

<a href="/contacts/{{contact.id}}/edit">Edit</a> <a href="/contacts">Back</a> </p>
-
-
-
-

We simply render a nice First Name and Last Name header, with the additional contact information below it, -and a couple of links: a link to edit the contact and a link to navigate back to the full list of contacts.

-
-
-
-

Editing And Deleting A Contact

-
-

Next up we will tackle the functionality on the other end of that “Edit” link. Editing a contact is going to look very -similar to creating a new contact. As with adding a new contact, we are going to need two routes that handle the same -path, but using different HTTP methods: a GET to /contacts/<contact_id>/edit will return a form allowing you to edit -the contact and a POST to that path will update it.

-
-
-

We are also going to piggyback the ability to delete a contact along with this editing functionality. To do this we -will need to handle a POST to /contacts/<contact_id>/delete.

-
-
-

Let’s look at the code to handle the GET, which, again, will return an HTML representation of an editing interface -for the given resource:

-
-
-
The Edit Contact Controller Code
-
-
@app.route("/contacts/<contact_id>/edit", methods=["GET"])
+                        
+
+
+

We simply render a nice First Name and Last Name header, with the additional contact + information below it, + and a couple of links: a link to edit the contact and a link to navigate back to the full + list of contacts.

+
+
+
+

Editing And Deleting A Contact

+
+

Next up we will tackle the functionality on the other end of that “Edit” link. + Editing a contact is going to look very + similar to creating a new contact. As with adding a new contact, we are going to need two + routes that handle the same + path, but using different HTTP methods: a GET to + /contacts/<contact_id>/edit will return a form allowing you to edit + the contact and a POST to that path will update it.

+
+
+

We are also going to piggyback the ability to delete a contact along with this editing + functionality. To do this we + will need to handle a POST to /contacts/<contact_id>/delete. +

+
+
+

Let’s look at the code to handle the GET, which, again, will return an + HTML representation of an editing interface + for the given resource:

+
+
+
The Edit Contact Controller Code
+
+
@app.route("/contacts/<contact_id>/edit", methods=["GET"])
 def contacts_edit_get(contact_id=0):
     contact = Contact.find(contact_id)
     return render_template("edit.html", contact=contact)
-
-
-
-

As you can see this looks an awful lot like our “Show Contact” functionality. In fact, it is nearly identical except -for the template that we render: here we render edit.html rather than show.html.

-
-
-

While our handler code looked similar to the “Show Contact” functionality, the edit.html template is going to look -very similar to the template for the “New Contact” functionality: we will have a form that submits updated contact -values to the same “edit” URL and that presents all the fields of a contact as inputs for editing, along with any error -messages.

-
-
-

Here is the first bit of the form:

-
-
-
The Edit Contact Form Start
-
-
    <form action="/contacts/{{ contact.id }}/edit" method="post"> (1)
+                        
+
+
+

As you can see this looks an awful lot like our “Show Contact” functionality. In + fact, it is nearly identical except + for the template that we render: here we render edit.html rather than + show.html.

+
+
+

While our handler code looked similar to the “Show Contact” functionality, the + edit.html template is going to look + very similar to the template for the “New Contact” functionality: we will have a + form that submits updated contact + values to the same “edit” URL and that presents all the fields of a contact as + inputs for editing, along with any error + messages.

+
+
+

Here is the first bit of the form:

+
+
+
The Edit Contact Form Start
+
+
    <form action="/contacts/{{ contact.id }}/edit" method="post"> (1)
         <fieldset>
             <legend>Contact Values</legend>
               <p>
@@ -4021,31 +7885,37 @@ messages.

<input name="email" id="email" type="text" placeholder="Email" value="{{ contact.email }}"> (2) <span class="error">{{ contact.errors['email'] }}</span> </p>
-
-
-
-
    -
  1. -

    Issue a POST to the /contacts/{{ contact.id }}/edit path

    -
  2. -
  3. -

    As with the new.html page, the input is tied to the contact’s email

    -
  4. -
-
-
-

This HTML is nearly identical to our new.html form, except that this form is going to submit a POST to a different -path, based on the id of the contact that we want to update. (It’s worth mentioning here that, rather than POST, we -would prefer to use a PUT or PATCH, but those are not available in plain HTML.)

-
-
-

Following this we have the remainder of our form, again very similar to the new.html template, and our submit button -to submit the form.

-
-
-
The Edit Contact Form Body
-
-
              <p>
+                        
+
+
+
    +
  1. +

    Issue a POST to the /contacts/{{ contact.id }}/edit path +

    +
  2. +
  3. +

    As with the new.html page, the input is tied to the contact’s + email

    +
  4. +
+
+
+

This HTML is nearly identical to our new.html form, except that this form is + going to submit a POST to a different + path, based on the id of the contact that we want to update. (It’s worth mentioning + here that, rather than POST, we + would prefer to use a PUT or PATCH, but those are not available in + plain HTML.)

+
+
+

Following this we have the remainder of our form, again very similar to the + new.html template, and our submit button + to submit the form.

+
+
+
The Edit Contact Form Body
+
+
              <p>
                   <label for="first_name">First Name</label>
                   <input name="first_name" id="first_name" type="text" placeholder="First Name"
                          value="{{ contact.first }}">
@@ -4065,71 +7935,90 @@ to submit the form.

<button>Save</button> </fieldset> </form>
-
-
-
-

In the final part of our template we have a small difference between the new.html and edit.html. Below the main -editing form, we include a second form that allows you to delete a contact. It does this by issuing a POST -to the /contacts/<contact id>/delete path. Just as we would prefer to use a PUT to update a contact, we would -much rather use an HTTP DELETE request to delete one. Unfortunately that also isn’t possible in plain HTML.

-
-
-

To finish up the page, there is a simple hyperlink back to the list of contacts.

-
-
-
The Edit Contact Form Footer
-
-
    <form action="/contacts/{{ contact.id }}/delete" method="post">
+                        
+
+
+

In the final part of our template we have a small difference between the + new.html and edit.html. Below the main + editing form, we include a second form that allows you to delete a contact. It does this by + issuing a POST + to the /contacts/<contact id>/delete path. Just as we would prefer to use + a PUT to update a contact, we would + much rather use an HTTP DELETE request to delete one. Unfortunately that also + isn’t possible in plain HTML.

+
+
+

To finish up the page, there is a simple hyperlink back to the list of contacts.

+
+
+
The Edit Contact Form Footer
+
+
    <form action="/contacts/{{ contact.id }}/delete" method="post">
         <button>Delete Contact</button>
     </form>
 
     <p>
         <a href="/contacts/">Back</a>
     </p>
-
-
-
-

Given all the similarities between the new.html and edit.html templates, you may be wondering why we are not -refactoring these two templates to share logic between them. That’s a good observation and, in a production system, -we would probably do just that.

-
-
-

For our purposes, however, since our application is small and simple, we will leave the templates separate.

-
- -
-
Handling The Post to /contacts/<contact_id>
-
-

Next we need to handle the HTTP POST request that the form in our edit.html template submits. We will declare -another route that handles the path as the GET above.

-
-
-

Here is the new handler code:

-
-
-
-
@app.route("/contacts/<contact_id>/edit", methods=["POST"]) (1)
+                        
+
+
+

Given all the similarities between the new.html and edit.html + templates, you may be wondering why we are not + refactoring these two templates to share logic between them. That’s a good + observation and, in a production system, + we would probably do just that. +

+
+
+

For our purposes, however, since our application is small and simple, we will leave the + templates separate.

+
+ +
+
Handling The Post to + /contacts/<contact_id>
+
+

Next we need to handle the HTTP POST request that the form in our + edit.html template submits. We will declare + another route that handles the path as the GET above.

+
+
+

Here is the new handler code:

+
+
+
+
@app.route("/contacts/<contact_id>/edit", methods=["POST"]) (1)
 def contacts_edit_post(contact_id=0):
     c = Contact.find(contact_id) (2)
     c.update(request.form['first_name'], request.form['last_name'], request.form['phone'], request.form['email']) (3)
@@ -4138,1066 +8027,1305 @@ def contacts_edit_post(contact_id=0):
         return redirect("/contacts/" + str(contact_id)) (5)
     else:
         return render_template("edit.html", contact=c) (6)
-
-
-
-
    -
  1. -

    Handle a POST to /contacts/<contact_id>/edit

    -
  2. -
  3. -

    Look the contact up by id

    -
  4. -
  5. -

    update the contact with the new information from the form

    -
  6. -
  7. -

    Attempt to save it

    -
  8. -
  9. -

    On success, flash a success message & redirect to the detail page

    -
  10. -
  11. -

    On failure, re-render the edit template, showing any errors

    -
  12. -
-
-
-

The logic in this handler is very similar to the logic in the handler for adding a new contact. The only real difference -is that, rather than creating a new Contact, we look the contact up by id and then call the update() method on it with -the values that were entered in the form.

-
-
-

Once again, this consistency between our CRUD operations is one of the nice and simplifying aspects of traditional CRUD web -applications.

-
-
-
-
-

Deleting A Contact

-
-

We piggybacked contact delete functionality into the same template used to edit a contact. This second form will issue -an HTTP POST to /contacts/<contact_id>/delete, and we will need to create a handler for that path as well.

-
-
-

Here is what the controller looks like:

-
-
-
The Delete Contact Controller Code
-
-
@app.route("/contacts/<contact_id>/delete", methods=["POST"]) (1)
+                            
+
+
+
    +
  1. +

    Handle a POST to /contacts/<contact_id>/edit

    +
  2. +
  3. +

    Look the contact up by id

    +
  4. +
  5. +

    update the contact with the new information from the form

    +
  6. +
  7. +

    Attempt to save it

    +
  8. +
  9. +

    On success, flash a success message & redirect to the detail page

    +
  10. +
  11. +

    On failure, re-render the edit template, showing any errors

    +
  12. +
+
+
+

The logic in this handler is very similar to the logic in the handler for adding a new + contact. The only real difference + is that, rather than creating a new Contact, we look the contact up by id and then call + the update() method on it with + the values that were entered in the form.

+
+
+

Once again, this consistency between our CRUD operations is one of the nice and + simplifying aspects of traditional CRUD web + applications.

+
+
+
+
+

Deleting A Contact

+
+

We piggybacked contact delete functionality into the same template used to edit a contact. + This second form will issue + an HTTP POST to /contacts/<contact_id>/delete, and we will + need to create a handler for that path as well.

+
+
+

Here is what the controller looks like:

+
+
+
The Delete Contact Controller Code
+
+
@app.route("/contacts/<contact_id>/delete", methods=["POST"]) (1)
 def contacts_delete(contact_id=0):
     contact = Contact.find(contact_id)
     contact.delete() (2)
     flash("Deleted Contact!")
     return redirect("/contacts") (3)
-
-
-
-
    -
  1. -

    Handle a POST the /contacts/<contact_id>/delete path

    -
  2. -
  3. -

    Look up and then invoke the delete() method on the contact

    -
  4. -
  5. -

    Flash a success message and redirect to the main list of contacts

    -
  6. -
-
-
-

The handler code is very simple since we don’t need to do any validation or conditional logic: we simply look up the -contact the same way we have been doing in our other handlers and invoke the delete() method on it, then redirect -back to the list of contacts with a success flash message.

-
-
-

No need for a template in this case, the contact is gone.

-
-
-
-

Contact.app…​ Implemented!

-
-

And, well…​ believe it or not, that’s our entire contact application! The Flask and Jinja2 code should be simple enough -that you are able to follow along, even if Python isn’t your preferred language or Flask isn’t your preferred web -application framework. Again, we don’t expect you to be a Python or Flask experts (we aren’t!) and you shouldn’t need -more than a basic understanding of how they work for the remainder of the book.

-
-
-

Now, admittedly, this isn’t a large or sophisticated application, but it does demonstrate many of the aspects of -traditional, web 1.0 applications: CRUD, the Post/Redirect/Get pattern, working -with domain logic in a controller, organizing our URLs in a coherent, resource-oriented manner.

-
-
-

And, furthermore, this is a deeply Hypermedia-Driven web application. Without thinking about it very much, we have -been using REST, HATEOAS and all the other hypermedia concepts we discussed earlier. We would bet that this simple -little contact app of ours is more RESTful than 99% of all JSON APIs ever built!

-
-
-

And it was all effortless: just by virtue of using a hypermedia, HTML, we naturally fall into the RESTful network -architecture.

-
-
-

So that’s great. But what’s the matter with this little web app? Why not end here and go off to develop the old web 1.0 style -applications people used to build?

-
-
-

Well, at some level, nothing is wrong with it. Particularly for an application that is as simple as this one it, the older -way of building web apps might be a perfectly acceptable approach!

-
-
-

However, our application does suffer from that “clunkiness” that we mentioned earlier when discussing web 1.0 applications: -every request replaces the entire screen, introducing a noticeable flicker when navigating between pages. You lose your -scroll state. You have to click around a bit more than you might in a more sophisticated web application.

-
-
-

Contact.app, at this point, just doesn’t feel like a “modern” web application, does it?

-
-
-

Is it time to reach for a JavaScript framework and JSON APIs to make our contact application more interactive?

-
-
-

No. No it isn’t.

-
-
-

It turns out that we can improve the user experience of this application without abandoning the -hypermedia architecture. In the next few chapters we will look at htmx, a hypermedia-oriented library -that will let us improve our contact application without abandoning the hypermedia approach we have used so far.

-
-
-
-
-
-

Hypermedia-Driven Web Applications With htmx

-
-

5. Extending HTML As Hypermedia

-
-
-

In the previous chapter we introduced a simple Web 1.0-style hypermedia application to manage contacts. Our application -supported the normal CRUD operations for contacts, as well as a simple mechanism for searching contacts. Our application -was built using nothing but forms and anchor tags, the traditional hypermedia controls used to interact with servers. -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.

-
-
-

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?

-
-
-

Unfortunately, our application has a few issues common to web 1.0 style applications:

-
-
-
    -
  • -

    From a user experience perspective: there is a noticeable refresh when you move between pages of the application, or when you create, update or -delete a contact. This is because every user interaction (link click or form submission) requires a full page -refresh, with a whole new HTML document to process after each action.

    -
  • -
  • -

    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 -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 -them the reputation for being “clunky” when compared with their more sophisticated JavaScript-based Single Page Application -cousins.

-
-
-

We could address this issue by adopting a Single Page Application framework, and updating our server-side to -provide JSON-based responses. Single Page Applications eliminate the clunkiness of web 1.0 applications by updating a -web page directly: they mutate the Document Object Model (DOM) directly, without doing a full page refresh.

-
- -
-

There are a few different styles of SPA, but, as we discussed in Chapter 1, the most common approach today is to tie -the DOM to a JavaScript model and then let an SPA framework like React or Vue -reactively update the DOM when a JavaScript model is updated: you make a change to a JavaScript object that is -stored locally in memory in the browser, and the web page “magically” updates its state to reflect the change in the -model.

-
-
-

In this style of application, communication with the server is typically done via a JSON Data API, -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.

-
-
-

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, -when using plain HTML, it is impossible to use HTTP fully.

-
-
- -
-

It turns out that we can actually boost the interactivity of our application without resorting to the SPA approach, by -using a library called htmx. 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

-
-
-

Consider again this simple anchor tag which, when interpreted by a browser, creates a hyperlink to the website for -this book:

-
-
-
A Simple Hyperlink, Again
-
-
<a href="https://hypermedia.systems/">
+                        
+
+
+
    +
  1. +

    Handle a POST the /contacts/<contact_id>/delete path +

    +
  2. +
  3. +

    Look up and then invoke the delete() method on the contact

    +
  4. +
  5. +

    Flash a success message and redirect to the main list of contacts

    +
  6. +
+
+
+

The handler code is very simple since we don’t need to do any validation or conditional + logic: we simply look up the + contact the same way we have been doing in our other handlers and invoke the + delete() method on it, then redirect + back to the list of contacts with a success flash message.

+
+
+

No need for a template in this case, the contact is gone.

+
+
+
+

Contact.app…​ Implemented!

+
+

And, well…​ believe it or not, that’s our entire contact application! The + Flask and Jinja2 code should be simple enough + that you are able to follow along, even if Python isn’t your preferred language or + Flask isn’t your preferred web + application framework. Again, we don’t expect you to be a Python or Flask experts (we + aren’t!) and you shouldn’t need + more than a basic understanding of how they work for the remainder of the book.

+
+
+

Now, admittedly, this isn’t a large or sophisticated application, but it does + demonstrate many of the aspects of + traditional, web 1.0 applications: CRUD, the Post/Redirect/Get pattern, working + with domain logic in a controller, organizing our URLs in a coherent, resource-oriented + manner.

+
+
+

And, furthermore, this is a deeply Hypermedia-Driven web application. Without + thinking about it very much, we have + been using REST, HATEOAS and all the other hypermedia concepts we discussed earlier. We + would bet that this simple + little contact app of ours is more RESTful than 99% of all JSON APIs ever built!

+
+
+

And it was all effortless: just by virtue of using a hypermedia, HTML, we naturally + fall into the RESTful network + architecture.

+
+
+

So that’s great. But what’s the matter with this little web app? Why not end here + and go off to develop the old web 1.0 style + applications people used to build?

+
+
+

Well, at some level, nothing is wrong with it. Particularly for an application that is as + simple as this one it, the older + way of building web apps might be a perfectly acceptable approach!

+
+
+

However, our application does suffer from that “clunkiness” that we mentioned + earlier when discussing web 1.0 applications: + every request replaces the entire screen, introducing a noticeable flicker when navigating + between pages. You lose your + scroll state. You have to click around a bit more than you might in a more sophisticated web + application.

+
+
+

Contact.app, at this point, just doesn’t feel like a “modern” web + application, does it?

+
+
+

Is it time to reach for a JavaScript framework and JSON APIs to make our contact application + more interactive?

+
+
+

No. No it isn’t.

+
+
+

It turns out that we can improve the user experience of this application without + abandoning the + hypermedia architecture. In the next few chapters we will look at htmx, a hypermedia-oriented library + that will let us improve our contact application without abandoning the hypermedia + approach we have used so far.

+
+
+
+
+
+

Hypermedia-Driven Web Applications With htmx +

+
+

5. Extending HTML As Hypermedia

+
+
+

In the previous chapter we introduced a simple Web 1.0-style hypermedia application to manage + contacts. Our application + supported the normal CRUD operations for contacts, as well as a simple mechanism for searching + contacts. Our application + was built using nothing but forms and anchor tags, the traditional hypermedia controls used to + interact with servers. + 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.

+
+
+

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?

+
+
+

Unfortunately, our application has a few issues common to web 1.0 style applications:

+
+
+
    +
  • +

    From a user experience perspective: there is a noticeable refresh when you move between pages + of the application, or when you create, update or + delete a contact. This is because every user interaction (link click or form submission) + requires a full page + refresh, with a whole new HTML document to process after each action.

    +
  • +
  • +

    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 + 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 + them the reputation for being “clunky” when compared with their more sophisticated + JavaScript-based Single Page Application + cousins.

+
+
+

We could address this issue by adopting a Single Page Application framework, and updating our + server-side to + provide JSON-based responses. Single Page Applications eliminate the clunkiness of web 1.0 + applications by updating a + web page directly: they mutate the Document Object Model (DOM) directly, without doing a full page + refresh.

+
+ +
+

There are a few different styles of SPA, but, as we discussed in Chapter 1, the most common approach + today is to tie + the DOM to a JavaScript model and then let an SPA framework like React or Vue + reactively update the DOM when a JavaScript model is updated: you make a change to a + JavaScript object that is + stored locally in memory in the browser, and the web page “magically” updates its state + to reflect the change in the + model. +

+
+
+

In this style of application, communication with the server is typically done via a JSON Data API, + 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.

+
+
+

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, + when using plain HTML, it is impossible to use HTTP fully.

+
+
+ +
+

It turns out that we can actually boost the interactivity of our application without resorting to + the SPA approach, by + using a library called htmx. 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

+
+
+

Consider again this simple anchor tag which, when interpreted by a browser, creates a hyperlink + to the website for + this book:

+
+
+
A Simple Hyperlink, Again
+
+
<a href="https://hypermedia.systems/">
   Hypermedia Systems
 </a>
-
-
-
-

Let’s break down in painstaking detail 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…​

    -
  • -
  • -

    The browser will issue an HTTP GET to https://hypermedia.systems…​

    -
  • -
  • -

    The browser will load the HTML body of the HTTP response into the browser window, replacing the current document.

    -
  • -
-
-
-

So we have four aspects of a simple hypermedia link like this, with the last three aspects supplying the mechanism that distinguishes -a hyperlink from “normal” text and makes this a hypermedia control.

-
-
-

Now, let’s take a moment and think about how we can generalize these last three aspects of a hyperlink.

-
-
-

Why Only Anchors & Forms?

-
-

An initial observation is: what makes anchor tags (and forms) so special?

-
-
-

Shouldn’t other elements be able to 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 -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:

-
-
-
Opportunity 1
-
-

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.

-
-
-

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 -you might want to use to issue an HTTP request.

-
-
-

Why shouldn’t these other events be able to trigger requests as well?

-
-
-

This gives us our second opportunity to expand the expressiveness of HTML:

-
-
-
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

-
-
-
-
-

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?

-
-
-

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 -other three: DELETE, PUT and PATCH.

-
-
-

Let’s recall what are all of these different HTTP request types 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

    -
  • -
-
-
-

These operations correspond closely to the CRUD operations we discussed in Chapter 2, and by only giving us access to 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:

-
-
-
Opportunity 3
-
-

HTML could be extended so that it could access these missing three HTTP methods, PUT, PATCH and DELETE.

-
-
-
-
-

Why Only Replace The Entire Screen?

-
-

As a final observation, consider the last aspect of a hyperlink: it replaces the entire screen when a user clicks on it.

-
-
-

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.

-
-
-

But there is no rule saying that hypermedia exchanges must replace the entire document.

-
-
-

This gives us our fourth, final and perhaps most important opportunity to generalize HTML:

-
-
-
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

-
-
-
-

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 -a Single Page Application, where only part of the DOM is updated by a given user interaction or network request.

-
-
-
-
-

5.2. 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 -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 Unpoly and Hotwire), but htmx is the purest of these libraries 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 -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.

-
-
-

Below is an example using the popular unpkg Content Delivery Network (CDN) to install version 1.7.0 -of the library. We use an integrity hash to ensure that the delivered JavaScript content matches what we expect. This -SHA can be found on the htmx website.

-
-
-

We also mark the script as crossorigin="anonymous" so no credentials will be sent to the CDN.

-
-
-
Installing htmx
-
-
<head>
+                    
+
+
+

Let’s break down in painstaking detail 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…​

    +
  • +
  • +

    The browser will issue an HTTP GET to + https://hypermedia.systems…​ +

    +
  • +
  • +

    The browser will load the HTML body of the HTTP response into the browser window, + replacing the current document.

    +
  • +
+
+
+

So we have four aspects of a simple hypermedia link like this, with the last three aspects + supplying the mechanism that distinguishes + a hyperlink from “normal” text and makes this a hypermedia control.

+
+
+

Now, let’s take a moment and think about how we can generalize these last three + aspects of a hyperlink.

+
+
+

Why Only Anchors & Forms?

+
+

An initial observation is: what makes anchor tags (and forms) so special?

+
+
+

Shouldn’t other elements be able to 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 + 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:

+
+
+
Opportunity 1
+
+

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.

+
+
+

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 + you might want to use to issue an HTTP request.

+
+
+

Why shouldn’t these other events be able to trigger requests as well?

+
+
+

This gives us our second opportunity to expand the expressiveness of HTML:

+
+
+
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

+
+
+
+
+

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?

+
+
+

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 + other three: DELETE, PUT and PATCH.

+
+
+

Let’s recall what are all of these different HTTP request types 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

    +
  • +
+
+
+

These operations correspond closely to the CRUD operations we discussed in Chapter 2, and by + only giving us access to 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:

+
+
+
Opportunity 3
+
+

HTML could be extended so that it could access these missing three HTTP methods, + PUT, PATCH and DELETE.

+
+
+
+
+

Why Only Replace The Entire Screen?

+
+

As a final observation, consider the last aspect of a hyperlink: it replaces the + entire screen when a user clicks on it.

+
+
+

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.

+
+
+

But there is no rule saying that hypermedia exchanges must replace the entire + document.

+
+
+

This gives us our fourth, final and perhaps most important opportunity to generalize HTML: +

+
+
+
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

+
+
+
+

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 + a Single Page Application, where only part of the DOM is updated by a given user interaction + or network request. +

+
+
+
+
+

5.2. 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 + 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 Unpoly and Hotwire), but htmx is the purest of these libraries 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 + 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.

+
+
+

Below is an example using the popular unpkg Content Delivery + Network (CDN) to install version 1.7.0 + of the library. We use an integrity hash to ensure that the delivered JavaScript content + matches what we expect. This + SHA can be found on the htmx website.

+
+
+

We also mark the script as crossorigin="anonymous" so no credentials will be + sent to the CDN.

+
+
+
Installing htmx
+
+
<head>
   <script src="https://unpkg.com/htmx.org@1.7.0"
           integrity="sha384-EzBXYPt0/T6gxNp0nuPtLkmRpmDBbjg6WmCUZRLXBBwYYmwAUxzlSGej0ARHX0Bo"
           crossorigin="anonymous"></script>
 
 </head>
-
-
-
-

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!

-
-
-

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!

-
-
-

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 -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.

-
-
-

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, -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 -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 -should be issued to.

-
-
-
-
-

5.3. Triggering HTTP Requests

-
-

Let’s look at the first feature of htmx: the ability for any element in a web page to issue HTTP requests. This is the -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

    -
  • -
-
-
-

Each of these attributes, when placed on an element, tell 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 -HTML:

-
-
-
A Simple htmx-Powered Button
-
-
<button hx-get="/contacts"> (1)
+                        
+
+
+

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!

+
+
+

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!

+
+
+

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 + 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. +

+
+
+

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, + 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 + 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 + should be issued to.

+
+
+
+
+

5.3. Triggering HTTP Requests

+
+

Let’s look at the first feature of htmx: the ability for any element in a web page to issue + HTTP requests. This is the + 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

    +
  • +
+
+
+

Each of these attributes, when placed on an element, tell 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 + HTML:

+
+
+
A Simple htmx-Powered Button
+
+
<button hx-get="/contacts"> (1)
   Get The Contacts
 </button>
-
-
-
-
    -
  1. -

    A simple button that issues an HTTP GET to /contacts

    -
  2. -
-
-
-

The htmx library will see the hx-get attribute on this button, and hook up some JavaScript logic to issue an HTTP -GET AJAX request to the /contacts path when the user clicks on it.

-
-
-

Very easy to understand and very consistent with the rest of 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 -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, -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.

-
- -
-

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:

-
-
-
A partial HTML Response to an htmx Request
-
-
<ul>
+                    
+
+
+
    +
  1. +

    A simple button that issues an HTTP GET to /contacts

    +
  2. +
+
+
+

The htmx library will see the hx-get attribute on this button, and hook up some + JavaScript logic to issue an HTTP + GET AJAX request to the /contacts path when the user clicks on it. +

+
+
+

Very easy to understand and very consistent with the rest of 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 + 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, + 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.

+
+ +
+

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:

+
+
+
A partial HTML Response to an htmx Request
+
+
<ul>
   <li><a href="mailto:joe@example.com">Joe</a></li>
   <li><a href="mailto:sarah@example.com">Sarah</a></li>
   <li><a href="mailto:fred@example.com">Fred</a></li>
 </ul>
-
-
-
-

This is just a simple 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 -this resource.

-
-
-

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 -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.

-
-
-
-
-

5.4. Targeting Other Elements

-
-

Now, given that htmx has issued a request and gotten back some HTML as a response, and that we are going to swap this -content into the existing page (rather than replacing the entire page), the question becomes: where should this new -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 -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 -new content should be placed. The value of the hx-target attribute is a Cascading Style Sheet (CSS) selector that -allows you to specify the element to put the new hypermedia content into.

-
-
-

Let’s add a div tag that encloses the button with the id main. We will then target this div with the response:

-
-
-
A Simple htmx-Powered Button
-
-
<div id="main"> (1)
+                        
+
+
+

This is just a simple 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 + this resource. +

+
+
+

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 + 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.

+
+
+
+
+

5.4. Targeting Other Elements

+
+

Now, given that htmx has issued a request and gotten back some HTML as a response, and that we + are going to swap this + content into the existing page (rather than replacing the entire page), the question becomes: + where should this new + 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 + 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 + new content should be placed. The value of the hx-target attribute is a Cascading + Style Sheet (CSS) selector that + allows you to specify the element to put the new hypermedia content into.

+
+
+

Let’s add a div tag that encloses the button with the id main. We + will then target this div with the response:

+
+
+
A Simple htmx-Powered Button
+
+
<div id="main"> (1)
 
   <button hx-get="/contacts" hx-target="#main"> (2)
     Get The Contacts
   </button>
 
 </div>
-
-
-
-
    -
  1. -

    A div element that wraps the button

    -
  2. -
  3. -

    The hx-target attribute that specifies the target of the response

    -
  4. -
-
-
-

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.

-
-
-

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?

-
-
-

It would look something like this:

-
-
-
Our HTML After the htmx Request Finishes
-
-
<div id="main">
+                    
+
+
+
    +
  1. +

    A div element that wraps the button

    +
  2. +
  3. +

    The hx-target attribute that specifies the target of the response

    +
  4. +
+
+
+

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.

+
+
+

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?

+
+
+

It would look something like this:

+
+
+
Our HTML After the htmx Request Finishes
+
+
<div id="main">
   <ul>
     <li><a href="mailto:joe@example.com">Joe</a></li>
     <li><a href="mailto:sarah@example.com">Sarah</a></li>
     <li><a href="mailto:fred@example.com">Fred</a></li>
   </ul>
 </div>
-
-
-
-

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.

-
-
-
-

5.5. Swap Styles

-
-

Now, perhaps we don’t want to simply 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

    -
  • -
-
-
-

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.

-
-
-

The next four values are taken from the Element.insertAdjacentHTML() DOM API, which allow you to place an element or -elements around a given element in various ways.

-
-
-

The last two values, delete and none are specific to htmx. The first option will remove the target element from the -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.

-
-
-

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.

-
-
-

To do so would require only a small change to our button, adding a new hx-swap attribute:

-
-
-
Replacing the Entire div
-
-
<div id="main">
+                    
+
+
+

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.

+
+
+
+

5.5. Swap Styles

+
+

Now, perhaps we don’t want to simply 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

    +
  • +
+
+
+

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.

+
+
+

The next four values are taken from the Element.insertAdjacentHTML() DOM API, which + allow you to place an element or + elements around a given element in various ways.

+
+
+

The last two values, delete and none are specific to htmx. The first + option will remove the target element from the + 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.

+
+
+

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.

+
+
+

To do so would require only a small change to our button, adding a new hx-swap + attribute:

+
+
+
Replacing the Entire div
+
+
<div id="main">
 
   <button hx-get="/contacts" hx-target="#main" hx-swap="outerHTML"> (1)
     Get The Contacts
   </button>
 
 </div>
-
-
-
-
    -
  1. -

    The hx-swap attribute specifies how to swap new content in

    -
  2. -
-
-
-

Now, when a response is received, the entire div will be replaced with the hypermedia content:

-
-
-
Our HTML After the htmx Request Finishes
-
-
<ul>
+                    
+
+
+
    +
  1. +

    The hx-swap attribute specifies how to swap new content in

    +
  2. +
+
+
+

Now, when a response is received, the entire div will be replaced with the hypermedia + content:

+
+
+
Our HTML After the htmx Request Finishes
+
+
<ul>
   <li><a href="mailto:joe@example.com">Joe</a></li>
   <li><a href="mailto:sarah@example.com">Sarah</a></li>
   <li><a href="mailto:fred@example.com">Fred</a></li>
 </ul>
-
-
-
-

You can see that, with this change, the target div has been entirely removed from the DOM, and the list that was returned -as the response has replaced it.

-
-
-

Later in the book we will see additional uses for hx-swap, for example when we implement infinite scrolling in our -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

    -
  • -
-
-
-

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

    -
  • -
-
-
-

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.

-
-
-
-

5.6. Using Events

-
-

Thus far we have been using a button to issue a request with htmx. You have probably intuitively understood that the -button would issue its request when you clicked on the button since, well, since that’s what you do with buttons: you -click on them.

-
-
-

And, yes, by default when an hx-get or another request-driving annotation from htmx is placed on a button, the request -will be issued when the button is clicked.

-
-
-

However, htmx generalizes this notion of an event triggering a request by using, you guessed it, another attribute: -hx-trigger. The hx-trigger attribute allows you to specify one or more events that will cause the element to -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:

-
-
-
    -
  • -

    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 -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.

-
-
-

To respond to a mouse entering the button, we would add the following attribute to our button:

-
-
-
A Bad Idea, But It Demonstrates The Concept!
-
-
<div id="main">
+                    
+
+
+

You can see that, with this change, the target div has been entirely removed from the DOM, and + the list that was returned + as the response has replaced it.

+
+
+

Later in the book we will see additional uses for hx-swap, for example when we + implement infinite scrolling in our + 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

    +
  • +
+
+
+

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

    +
  • +
+
+
+

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.

+
+
+
+

5.6. Using Events

+
+

Thus far we have been using a button to issue a request with htmx. You have probably intuitively + understood that the + button would issue its request when you clicked on the button since, well, since that’s + what you do with buttons: you + click on them.

+
+
+

And, yes, by default when an hx-get or another request-driving annotation from htmx + is placed on a button, the request + will be issued when the button is clicked.

+
+
+

However, htmx generalizes this notion of an event triggering a request by using, you guessed it, + another attribute: + hx-trigger. The hx-trigger attribute allows you to specify one or more + events that will cause the element to + 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:

+
+
+
    +
  • +

    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 + 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.

+
+
+

To respond to a mouse entering the button, we would add the following attribute to our button: +

+
+
+
A Bad Idea, But It Demonstrates The Concept!
+
+
<div id="main">
 
   <button hx-get="/contacts" hx-target="#main" hx-swap="outerHTML" hx-trigger="mouseenter"> (1)
     Get The Contacts
   </button>
 
 </div>
-
-
-
-
    -
  1. -

    Issue a request…​ on the mouseenter event?

    -
  2. -
-
-
-

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 -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 -event by adding square brackets after it: someEvent[someFilter]. The filter itself is a JavaScript expression that -will be evaluated when the given event occurs. If the result is truthy, in the JavaScript sense, it will trigger the -request. If not, the request will not be triggered.

-
-
-

In the case of keyboard shortcuts, we want to catch the keyup event in addition to the click event:

-
-
-
A Start
-
-
<div id="main">
+                    
+
+
+
    +
  1. +

    Issue a request…​ on the mouseenter event?

    +
  2. +
+
+
+

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 + 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 + event by adding square brackets after it: someEvent[someFilter]. The filter itself + is a JavaScript expression that + will be evaluated when the given event occurs. If the result is truthy, in the JavaScript sense, + it will trigger the + request. If not, the request will not be triggered.

+
+
+

In the case of keyboard shortcuts, we want to catch the keyup event in addition to + the click event:

+
+
+
A Start
+
+
<div id="main">
 
   <button hx-get="/contacts" hx-target="#main" hx-swap="outerHTML" hx-trigger="click, keyup"> (1)
     Get The Contacts
   </button>
 
 </div>
-
-
-
-
    -
  1. -

    A trigger with two events

    -
  2. -
-
-
-

Note that we have a comma separated list of events that can trigger this element, allowing us to respond to more than -one potential triggering event. We still want to respond to the click event and load the contacts, in addition -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!)

-
-
-

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:

-
-
-
Better!
-
-
<div id="main">
+                    
+
+
+
    +
  1. +

    A trigger with two events

    +
  2. +
+
+
+

Note that we have a comma separated list of events that can trigger this element, allowing us to + respond to more than + one potential triggering event. We still want to respond to the click event and + load the contacts, in addition + 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!)

+
+
+

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:

+
+
+
Better!
+
+
<div id="main">
 
   <button hx-get="/contacts" hx-target="#main" hx-swap="outerHTML" hx-trigger="click, keyup[ctrlKey && key == 'l']"> (1)
     Get The Contacts
   </button>
 
 </div>
-
-
-
-
    -
  1. -

    keyup now has a filter, so the control key and L must be pressed

    -
  2. -
-
-
-

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 -rather than the global name space, so you can easily filter on the properties of a given event. You can use any expression -you like for a filter, however: calling a global JavaScript function, for example, is perfectly acceptable.

-
-
-

OK, so this filter limits the keyup events that will trigger the request to only Ctrl-L presses. However, we still have -the problem that, as it stands, only keyup events within the button will trigger the request.

-
-
-

If you are not familiar with the JavaScript event bubbling model: events typically “bubble” up to parent elements. So an -event like keyup will be triggered first on the focused element, and then on its parent (enclosing) element, and so -on, until it reaches the top level document object that is the root of all other elements.

-
-
-

To support a global keyboard shortcut that works regardless of what element has focus, we will take advantage of -event bubbling and a feature that the hx-trigger attribute supports: the ability to listen to other elements for -events. The syntax for doing this is the from: modifier, which is added after an event name and that allows you to -specify a specific element to listen for the given event on using a CSS selector.

-
-
-

In this case, we want to listen to the body element, which is the parent element of all visible elements on the page.

-
-
-

Here is what our updated hx-trigger attribute looks like:

-
-
-
Better!
-
-
<div id="main">
+                    
+
+
+
    +
  1. +

    keyup now has a filter, so the control key and L must be pressed

    +
  2. +
+
+
+

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 + rather than the global name space, so you can easily filter on the properties of a given event. + You can use any expression + you like for a filter, however: calling a global JavaScript function, for example, is perfectly + acceptable.

+
+
+

OK, so this filter limits the keyup events that will trigger the request to only + Ctrl-L presses. However, we still have + the problem that, as it stands, only keyup events within the button will + trigger the request.

+
+
+

If you are not familiar with the JavaScript event bubbling model: events typically + “bubble” up to parent elements. So an + event like keyup will be triggered first on the focused element, and then on its + parent (enclosing) element, and so + on, until it reaches the top level document object that is the root of all other + elements.

+
+
+

To support a global keyboard shortcut that works regardless of what element has focus, we will + take advantage of + event bubbling and a feature that the hx-trigger attribute supports: the ability to + listen to other elements for + events. The syntax for doing this is the from: modifier, which is added after an + event name and that allows you to + specify a specific element to listen for the given event on using a CSS selector.

+
+
+

In this case, we want to listen to the body element, which is the parent element of + all visible elements on the page.

+
+
+

Here is what our updated hx-trigger attribute looks like:

+
+
+
Better!
+
+
<div id="main">
 
   <button hx-get="/contacts" hx-target="#main" hx-swap="outerHTML" hx-trigger="click, keyup[ctrlKey && key == 'L'] from:body">(1)
     Get The Contacts
   </button>
 
 </div>
-
-
-
-
    -
  1. -

    Listen to the event on the body tag

    -
  2. -
-
-
-

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.

-
-
-

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.

-
-
-

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 -in a way that adding significant amounts of JavaScript does not.

-
-
-
-

5.7. htmx: HTML eXtended

-
-

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

    -
  • -
-
-
-

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:

-
-
-
Opportunities For Improving HTML
-
-
Any element should be able to make HTTP requests
-
-

hx-get, hx-post, hx-put, hx-patch, hx-delete

-
-
Any event should be able to trigger an HTTP request
-
-

hx-trigger

-
-
Any HTTP Action should be available
-
-

hx-put, hx-patch, hx-delete

-
-
Any place on the page should be replaceable (transclusion)
-
-

hx-target, hx-swap

-
-
-
-
-
-

5.8. Passing Request Parameters

-
-

So far we have been just looking at a situation where a button makes a simple GET request. This is conceptually very -close to what an anchor tag might do. But there is that other native hypermedia control in HTML-based applications: -forms. Forms are used to pass additional information beyond just a URL up to the server in a request.

-
-
-

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 -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 -tag.

-
-
-

Let’s take our original button for retrieving contacts and repurpose it for searching contacts:

-
-
-
A Simple htmx-Powered Button
-
-
<div id="main">
+                    
+
+
+
    +
  1. +

    Listen to the event on the body tag

    +
  2. +
+
+
+

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.

+
+
+

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.

+
+
+

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 + in a way that adding significant amounts of JavaScript does not. +

+
+
+
+

5.7. htmx: HTML eXtended

+
+

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

    +
  • +
+
+
+

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: +

+
+
+
Opportunities For Improving HTML
+
+
Any element should be able to make HTTP requests
+
+

hx-get, hx-post, hx-put, hx-patch, + hx-delete

+
+
Any event should be able to trigger an HTTP request
+
+

hx-trigger

+
+
Any HTTP Action should be available
+
+

hx-put, hx-patch, hx-delete

+
+
Any place on the page should be replaceable (transclusion)
+
+

hx-target, hx-swap

+
+
+
+
+
+

5.8. Passing Request Parameters

+
+

So far we have been just looking at a situation where a button makes a simple GET + request. This is conceptually very + close to what an anchor tag might do. But there is that other native hypermedia control in + HTML-based applications: + forms. Forms are used to pass additional information beyond just a URL up to the server in a + request.

+
+
+

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 + 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 + tag.

+
+
+

Let’s take our original button for retrieving contacts and repurpose it for searching + contacts:

+
+
+
A Simple htmx-Powered Button
+
+
<div id="main">
 
   <form> (1)
       <label for="search">Search Contacts:</label>
@@ -5208,62 +9336,78 @@ tag.

</form> </div>
-
-
-
-
    -
  1. -

    With an enclosing form tag, all inputs values will be submitted

    -
  2. -
  3. -

    A new input that users will be able to enter search text into

    -
  4. -
  5. -

    Our button has been converted to an hx-post

    -
  6. -
-
-
-

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.

-
-
-

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 -is triggered, htmx will look up the DOM hierarchy for an enclosing form, and, if one is found, it will include all -values from within that form. (This is sometimes referred to as “serializing” the form.)

-
-
-

You might have noticed that the button was switched from a GET request to a POST request. This is because, by default, -htmx does not include the closest enclosing form for GET requests, but it does include the form for all other types -of requests.

-
-
-

This may seem a little strange, but it avoids junking up URLs that are used within form when dealing with history -entries, which we will discuss in a bit. And you can always include an enclosing form’s values with an element that -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 -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 -row of data in a table.

-
-
-

To address this issue, htmx provides another 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:

-
-
-
A Simple htmx-Powered Button
-
-
<div id="main">
+                        
+
+
+
    +
  1. +

    With an enclosing form tag, all inputs values will be submitted

    +
  2. +
  3. +

    A new input that users will be able to enter search text into

    +
  4. +
  5. +

    Our button has been converted to an hx-post

    +
  6. +
+
+
+

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.

+
+
+

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 + is triggered, htmx will look up the DOM hierarchy for an enclosing form, and, if one is + found, it will include all + values from within that form. (This is sometimes referred to as “serializing” + the form.)

+
+
+

You might have noticed that the button was switched from a GET request to a + POST request. This is because, by default, + htmx does not include the closest enclosing form for GET requests, but + it does include the form for all other types + of requests.

+
+
+

This may seem a little strange, but it avoids junking up URLs that are used within form when + dealing with history + entries, which we will discuss in a bit. And you can always include an enclosing + form’s values with an element that + 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 + 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 + row of data in a table. +

+
+
+

To address this issue, htmx provides another 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:

+
+
+
A Simple htmx-Powered Button
+
+
<div id="main">
 
   <label for="search">Search Contacts:</label>
   <input id="search" name="q" type="search" placeholder="Search Contacts">
@@ -5272,954 +9416,1193 @@ The hx-include attribute allows you to select input values that you
   </button>
 
 </div>
-
-
-
-
    -
  1. -

    hx-include can be used to include values directly in a request

    -
  2. -
-
-
-

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.

-
-
-

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.

-
-
-
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:

-
-
-
-
closest
-
-

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

-
-
previous
-
-

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

-
-
this
-
-

the current element

-
-
-
-
-

Using relative CSS selectors often allows you to avoid needing to generate ids for elements, since you can take advantage -of their local structural layout instead.

-
-
-
-
-

Inline Values

-
-

A final way to include values in htmx-driven requests is to use the hx-vals attribute, which allows you to include -“static” values in the request. This can be useful if you have additional information that you want to include in -requests, but you don’t want to have this information embedded in, for example, hidden inputs (which would be the -standard mechanism for including additional, hidden information in HTML.)

-
-
-

Here is an example of hx-vals:

-
-
-
A Simple htmx-Powered Button
-
-
<button hx-get="/contacts" hx-vals='{"state":"MT"}'> (1)
+                        
+
+
+
    +
  1. +

    hx-include can be used to include values directly in a request

    +
  2. +
+
+
+

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.

+
+
+

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.

+
+
+
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:

+
+
+
+
closest
+
+

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

+
+
previous
+
+

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

+
+
this
+
+

the current element

+
+
+
+
+

Using relative CSS selectors often allows you to avoid needing to generate ids for + elements, since you can take advantage + of their local structural layout instead.

+
+
+
+
+

Inline Values

+
+

A final way to include values in htmx-driven requests is to use the hx-vals + attribute, which allows you to include + “static” values in the request. This can be useful if you have additional + information that you want to include in + requests, but you don’t want to have this information embedded in, for example, hidden + inputs (which would be the + standard mechanism for including additional, hidden information in HTML.)

+
+
+

Here is an example of hx-vals:

+
+
+
A Simple htmx-Powered Button
+
+
<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

    -
  2. -
-
-
-

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 -around its value. This is because JSON strictly requires double quotes and, therefore, to avoid escaping we needed to -use the single-quote form for the attribute value.

-
-
-

You can also prefix hx-vals with a js: and pass values evaluated at the time of the request, which can be useful for -including things like a dynamically maintained variable, or value from a third party JavaScript library.

-
-
-

For example, if the state variable were maintained dynamically, via some JavaScript, and there existed a JavaScript -function, getCurrentState(), that returned the currently selected state, it could be included dynamically in htmx -requests like so:

-
-
-
A Dynamic Value
-
-
<button hx-get="/contacts" hx-vals='js:{"state":getCurrentState()}'> (1)
+                        
+
+
+
    +
  1. +

    hx-vals, a JSON value to include in the request

    +
  2. +
+
+
+

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 + around its value. This is because JSON strictly requires double quotes and, therefore, to + avoid escaping we needed to + use the single-quote form for the attribute value.

+
+
+

You can also prefix hx-vals with a js: and pass values evaluated at + the time of the request, which can be useful for + including things like a dynamically maintained variable, or value from a third party + JavaScript library.

+
+
+

For example, if the state variable were maintained dynamically, via some + JavaScript, and there existed a JavaScript + function, getCurrentState(), that returned the currently selected state, it + could be included dynamically in htmx + requests like so:

+
+
+
A Dynamic Value
+
+
<button hx-get="/contacts" hx-vals='js:{"state":getCurrentState()}'> (1)
   Get The Contacts In The Selected State
 </button>
-
-
-
-
    -
  1. -

    With the js: prefix, this expression will evaluate at submit time

    -
  2. -
-
-
-

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 -the spirit of HTML, while also giving you the flexibility to achieve what you want.

-
-
-
-
-

5.9. History Support

-
-

A final piece of functionality to discuss to close out our overview of htmx is 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 -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.

-
-
-

However, there are likely to be a lot of AJAX driven interactions in a Single Page Application where it is appropriate -to create a history entry. And, it turns out, there is a JavaScript API for working with the history of a browser. -Unfortunately, this API is deeply annoying and difficult to work with and, thus, is often ignored by JavaScript developers.

-
-
-

If you have ever used a Single Page Application and accidentally clicked the back button, only to lose your entire -application state and have to start over, you have seen this problem in action.

-
-
-

In htmx, as with Single Page Application frameworks, you will often need to explicitly work with the history API. -Fortunately, since htmx sticks so close to the original model of the web and since it is declarative, getting web history -right is typically much easier to do in an htmx-based application.

-
-
-

Consider the button we have been looking at to load contacts:

-
-
-
Our trusty button
-
-
<button hx-get="/contacts" hx-target="#main">
+                        
+
+
+
    +
  1. +

    With the js: prefix, this expression will evaluate at submit time

    +
  2. +
+
+
+

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 + the spirit of HTML, while also giving you the flexibility to achieve what you want.

+
+
+
+
+

5.9. History Support

+
+

A final piece of functionality to discuss to close out our overview of htmx is 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 + 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.

+
+
+

However, there are likely to be a lot of AJAX driven interactions in a Single Page Application + where it is appropriate + to create a history entry. And, it turns out, there is a JavaScript API for working with the + history of a browser. + Unfortunately, this API is deeply annoying and difficult to work with and, thus, is often + ignored by JavaScript developers.

+
+
+

If you have ever used a Single Page Application and accidentally clicked the back button, only to + lose your entire + application state and have to start over, you have seen this problem in action.

+
+
+

In htmx, as with Single Page Application frameworks, you will often need to explicitly work with + the history API. + Fortunately, since htmx sticks so close to the original model of the web and since it is + declarative, getting web history + right is typically much easier to do in an htmx-based application.

+
+
+

Consider the button we have been looking at to load contacts:

+
+
+
Our trusty button
+
+
<button hx-get="/contacts" hx-target="#main">
   Get The Contacts
 </button>
-
-
-
-

As it stands, if you click this button it will retrieve the content from /contacts and load it into the element with the -id main, but it will not create a new history entry.

-
-
-

If we wanted it to create a history entry when this request happened, we would add a new attribute to the button, the -hx-push-url attribute:

-
-
-
Our trusty button, now with history!
-
-
<button hx-get="/contacts" hx-target="#main" hx-push-url="true"> (1)
+                    
+
+
+

As it stands, if you click this button it will retrieve the content from /contacts + and load it into the element with the + id main, but it will not create a new history entry.

+
+
+

If we wanted it to create a history entry when this request happened, we would add a new + attribute to the button, the + hx-push-url attribute: +

+
+
+
Our trusty button, now with history!
+
+
<button hx-get="/contacts" hx-target="#main" hx-push-url="true"> (1)
   Get The Contacts
 </button>
-
-
-
-
    -
  1. -

    hx-push-url will create an entry in history when the button is clicked

    -
  2. -
-
-
-

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 -restored, along with the original URL.

-
-
-

Now, the name hx-push-url for this attribute might sound a little obscure, but it is based on the JavaScript API, -history.pushState(). This notion of “pushing” derives from the fact that history entries are modeled as a stack, and -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!

-
-
-

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 -on the /contacts page?

-
-
-

In this case, you will need to handle the htmx-based “partial” response as well as the non-htmx “full page” response. You -can do this using HTTP headers, a topic we will go into in detail later in the book.

-
-
-
-

5.10. 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.

-
-
-

htmx is a very pure extension to HTML, aiming to incrementally improve the language 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 -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 -in-page pop-ups that sit “on top” of the existing page. (Of course, in reality, this is an optical illusion and it is -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 -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.

-
-
-

By staying closer to the original model of the web, htmx aims to strike a balance between simplicity and functionality, -deferring to other libraries for more elaborate front-end extensions on top of the existing web platform. The good news -is that htmx plays well with others, so when these needs arise it is often easy enough to bring in another library to handle -them.

-
-
-
-
-
-

6. htmx Patterns

-
-
-

Now that we’ve seen how htmx extends HTML as a hypermedia, it’s time to put it into action. As we use htmx, we will still -be using hypermedia: we will issue HTTP requests and get back HTML. But, with the additional functionality that htmx provides, -we will have a more powerful hypermedia to work with, allowing us to accomplish much more sophisticated interfaces.

-
-
-

This will allow us to address user experience issues, such as long feedback cycles or painful page refreshes, without -needing to write much, if any, JavaScript, and without creating a JSON API. Everything will be implemented in hypermedia, -using the core concepts of the original web.

-
-
-

6.1. Installing htmx

-
-

The first thing we need to do is install htmx in our web application. We are going to do this by downloading the -source and saving it locally in our application, so we aren’t dependent on any external systems. This is known as “vendoring” -the library. We can grab the latest version of htmx by navigating our browser to https://unpkg.com/htmx.org, which will -redirect us to the source of the latest version of the library.

-
-
-

We can save the content from this URL into the static/js/htmx.js file in our project.

-
-
-

You can, of course, use a more sophisticated JavaScript package manager such as Node Package Manager (NPM) or yarn to install -htmx. You do this by referring to its package name, htmx.org, in the manner appropriate for your tool. However, -htmx is very small (approximately 12kb when compressed and zipped) and is dependency free, so using it does not require -an elaborate mechanism or build tool.

-
-
-

With htmx downloaded locally to our applications /static/js directory, we can now load it in to our application. -We do this by adding the following script tag to the head tag in our layout.html file, which will make htmx -available and active on every page in our application:

-
-
-
Installing htmx
-
-
  <head>
+                    
+
+
+
    +
  1. +

    hx-push-url will create an entry in history when the button is clicked

    +
  2. +
+
+
+

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 + restored, along with the original URL.

+
+
+

Now, the name hx-push-url for this attribute might sound a little obscure, but it is + based on the JavaScript API, + history.pushState(). This notion of “pushing” derives from the fact + that history entries are modeled as a stack, and + 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!

+
+
+

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 + on the /contacts page?

+
+
+

In this case, you will need to handle the htmx-based “partial” response as well as + the non-htmx “full page” response. You + can do this using HTTP headers, a topic we will go into in detail later in the book.

+
+
+
+

5.10. 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.

+
+
+

htmx is a very pure extension to HTML, aiming to incrementally improve the language 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 + 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 + in-page pop-ups that sit “on top” of the existing page. (Of course, in reality, this + is an optical illusion and it is + 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 + 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.

+
+
+

By staying closer to the original model of the web, htmx aims to strike a balance between + simplicity and functionality, + deferring to other libraries for more elaborate front-end extensions on top of the existing web + platform. The good news + is that htmx plays well with others, so when these needs arise it is often easy enough to bring + in another library to handle + them.

+
+
+
+
+
+

6. htmx Patterns

+
+
+

Now that we’ve seen how htmx extends HTML as a hypermedia, it’s time to put it into + action. As we use htmx, we will still + be using hypermedia: we will issue HTTP requests and get back HTML. But, with the additional + functionality that htmx provides, + we will have a more powerful hypermedia to work with, allowing us to accomplish much more + sophisticated interfaces.

+
+
+

This will allow us to address user experience issues, such as long feedback cycles or painful page + refreshes, without + needing to write much, if any, JavaScript, and without creating a JSON API. Everything will be + implemented in hypermedia, + using the core concepts of the original web.

+
+
+

6.1. Installing htmx

+
+

The first thing we need to do is install htmx in our web application. We are going to do this by + downloading the + source and saving it locally in our application, so we aren’t dependent on any external + systems. This is known as “vendoring” + the library. We can grab the latest version of htmx by navigating our browser to + https://unpkg.com/htmx.org, + which will + redirect us to the source of the latest version of the library.

+
+
+

We can save the content from this URL into the static/js/htmx.js file in our + project.

+
+
+

You can, of course, use a more sophisticated JavaScript package manager such as Node Package + Manager (NPM) or yarn to install + htmx. You do this by referring to its package name, htmx.org, in the manner + appropriate for your tool. However, + htmx is very small (approximately 12kb when compressed and zipped) and is dependency free, so + using it does not require + an elaborate mechanism or build tool.

+
+
+

With htmx downloaded locally to our applications /static/js directory, we can now + load it in to our application. + We do this by adding the following script tag to the head tag in our + layout.html file, which will make htmx + available and active on every page in our application:

+
+
+
Installing htmx
+
+
  <head>
     <script src="/js/htmx.js"></script>
     ...
   </head>
-
-
-
-

Recall that the layout.html file is a layout file included in most templates that wraps the content of those templates -in common HTML, including a head element that we are using here to install htmx.

-
-
-

Believe it or not, that’s it! This simple script tag will make htmx’s functionality available across our entire application.

-
-
-
-

6.2. AJAX-ifying Our Application

-
-

To get our feet wet with htmx, the first feature we are going to take advantage of, is what is known as “boosting”. This is -a bit of a “cheater” feature of htmx in that we don’t need to do much beyond adding a single attribute, hx-boost, to the -application. This hx-boost attribute is unlike most other attributes in htmx: whereas other htmx attributes tend to be -very focused on one aspect of improving HTML (e.g. hx-trigger focuses on the events that trigger a request, hx-swap focuses on how responses -are swapped into the DOM, etc.) the hx-boost attribute, in contrast, is a high-level attribute.

-
-
-

When you put hx-boost on a given element with the value true, it will “boost” all anchor and form elements within that -element. “Boost”, here, means that htmx will convert all those anchors and forms from “normal” hypermedia controls -into AJAX-powered hypermedia controls. Rather than issuing “normal” HTTP requests that replace the whole page, the links -and forms will issue AJAX requests and then htmx will swap the inner content of the <body> tag in the response to these -requests into the existing pages <body> tag.

-
-
-

This makes navigation feel faster because the browser will not be re-interpreting most of the tags in the response -<head> and so forth.

-
-
- -
-

Let’s take a look at an example of a boosted link. Below is a link to a hypothetical settings page for a web application. -Because it has hx-boost="true" on it, htmx will halt the normal link behavior of issuing a request to the /settings path and replacing -the entire page with the response. Instead, htmx will issue an AJAX request to /settings, taking the result and replacing -the body element with the new content.

-
-
-
A Boosted Link
-
-
<a href="/settings" hx-boost="true">Settings</a> (1)
-
-
-
-
    -
  1. -

    A simple attribute makes this link AJAX-powered

    -
  2. -
-
-
-

You might reasonably ask: what’s the advantage here? We are issuing an AJAX request and simply replacing the entire body.

-
-
-

Is that significantly different from just issuing a normal link request?

-
-
-

Yes, it is in fact different: with a boosted link, the browser is able to avoid any processing associated with the head tag. The head -tag often contains many scripts and CSS file references. In the boosted scenario, it is not necessary to re-process those -resources: the scripts and styles have already been processed and will continue to apply to the new content. This can -often be a very easy way to speed up your hypermedia application.

-
-
-

A second question you might have is: does the response need to be formatted specially to work with hx-boost? After all, -the settings page would normally render an html tag, with a head tag and so forth. Do you need to handle “boosted” -requests specially?

-
-
-

The answer is no: htmx is smart enough to pull out only the content of the body tag to swap in to the new page. -The head tag is mostly ignored: only the title tag, if it is present, will be processed. This means you don’t need to -do anything special on the server side to render templates that hx-boost can handle: just return the normal HTML for -your page, and it should work fine.

-
-
-

Note that boosted links (and forms) will also continue to update the navigation bar and history, just like normal links, -so users will be able to use the browser back button, will be able to copy and paste URLs (or “deep links”) and so on. -Links will act pretty much like “normal”, they will just be faster.

-
-
-
-

Boosted Forms

-
-

Boosted form tags work in a similar way to boosted anchor tags: a boosted form will use an AJAX request rather than the -usual browser-issued request, and will replace the entire body with the response:

-
-
-

Here is an example of a form that posts messages to the /messages end point using an HTTP POST request. By adding -hx-boost to it, those requests will be done in AJAX, rather than the normal browser behavior.

-
-
-
A Boosted Form
-
-
<form action="/messages" method="post" hx-boost="true">(1)
+                    
+
+
+

Recall that the layout.html file is a layout file included in most + templates that wraps the content of those templates + in common HTML, including a head element that we are using here to install htmx. +

+
+
+

Believe it or not, that’s it! This simple script tag will make htmx’s functionality + available across our entire application.

+
+
+
+

6.2. AJAX-ifying Our Application

+
+

To get our feet wet with htmx, the first feature we are going to take advantage of, is what is + known as “boosting”. This is + a bit of a “cheater” feature of htmx in that we don’t need to do much beyond + adding a single attribute, hx-boost, to the + application. This hx-boost attribute is unlike most other attributes in htmx: + whereas other htmx attributes tend to be + very focused on one aspect of improving HTML (e.g. hx-trigger focuses on the events + that trigger a request, hx-swap focuses on how responses + are swapped into the DOM, etc.) the hx-boost attribute, in contrast, is a + high-level attribute.

+
+
+

When you put hx-boost on a given element with the value true, it will + “boost” all anchor and form elements within that + element. “Boost”, here, means that htmx will convert all those anchors and forms + from “normal” hypermedia controls + into AJAX-powered hypermedia controls. Rather than issuing “normal” HTTP requests + that replace the whole page, the links + and forms will issue AJAX requests and then htmx will swap the inner content of the + <body> tag in the response to these + requests into the existing pages <body> tag.

+
+
+

This makes navigation feel faster because the browser will not be re-interpreting most of the + tags in the response + <head> and so forth. +

+
+
+ +
+

Let’s take a look at an example of a boosted link. Below is a link to a hypothetical + settings page for a web application. + Because it has hx-boost="true" on it, htmx will halt the normal link behavior + of issuing a request to the /settings path and replacing + the entire page with the response. Instead, htmx will issue an AJAX request to + /settings, taking the result and replacing + the body element with the new content.

+
+
+
A Boosted Link
+
+
<a href="/settings" hx-boost="true">Settings</a> (1)
+
+
+
+
    +
  1. +

    A simple attribute makes this link AJAX-powered

    +
  2. +
+
+
+

You might reasonably ask: what’s the advantage here? We are issuing an AJAX request and + simply replacing the entire body.

+
+
+

Is that significantly different from just issuing a normal link request?

+
+
+

Yes, it is in fact different: with a boosted link, the browser is able to avoid any + processing associated with the head tag. The head + tag often contains many scripts and CSS file references. In the boosted scenario, it is not + necessary to re-process those + resources: the scripts and styles have already been processed and will continue to apply to + the new content. This can + often be a very easy way to speed up your hypermedia application.

+
+
+

A second question you might have is: does the response need to be formatted specially to work + with hx-boost? After all, + the settings page would normally render an html tag, with a head + tag and so forth. Do you need to handle “boosted” + requests specially?

+
+
+

The answer is no: htmx is smart enough to pull out only the content of the body + tag to swap in to the new page. + The head tag is mostly ignored: only the title tag, if it is present, will be + processed. This means you don’t need to + do anything special on the server side to render templates that hx-boost can + handle: just return the normal HTML for + your page, and it should work fine.

+
+
+

Note that boosted links (and forms) will also continue to update the navigation bar and + history, just like normal links, + so users will be able to use the browser back button, will be able to copy and paste URLs + (or “deep links”) and so on. + Links will act pretty much like “normal”, they will just be faster.

+
+
+
+

Boosted Forms

+
+

Boosted form tags work in a similar way to boosted anchor tags: a boosted form will use an + AJAX request rather than the + usual browser-issued request, and will replace the entire body with the response:

+
+
+

Here is an example of a form that posts messages to the /messages end point + using an HTTP POST request. By adding + hx-boost to it, those requests will be done in AJAX, rather than the normal + browser behavior. +

+
+
+
A Boosted Form
+
+
<form action="/messages" method="post" hx-boost="true">(1)
   <input type="text" name="message" placeholder="Enter A Message...">
   <button>Post Your Message</button>
 </form>
-
-
-
-
    -
  1. -

    As with the link, a simple attribute makes this form AJAX-powered

    -
  2. -
-
-
-

A big advantage of the AJAX-based request that hx-boost uses (and the lack of head processing that occurs) is that it -avoids what is known as a flash of unstyled content:

-
-
-
-
Flash Of Unstyled Content (FOUC)
-
-

A situation where a browser renders a web page before all the styling information is -available for the page. A FOUC causes a disconcerting momentary “flash” of the unstyled content, which is then restyled -when all the style information is available. You will notice this as a flicker when you move around the internet: text, -images and other content can “jump around” on the page as styles are applied to it.

-
-
-
-
-

With hx-boost the site’s styling is already loaded before the new content is retrieved, so there is no such flash of -unstyled content. This can make a “boosted” application feel both smoother and also snappier in general.

-
-
-
-

Attribute Inheritance

-
-

Let’s expand on our previous example of a boosted link, and add a few more boosted links alongside it. We add links -such that we have one to the /contacts page, the one to the /settings page, and one to the /help page. All these -links are boosted and will behave in the manner that we have described above.

-
-
-

This feels a little redundant, doesn’t it? It seems silly to annotate all three links with the hx-boost="true" attribute -right next to one another.

-
-
-
A Set of Boosted Links
-
-
<a href="/contacts" hx-boost="true">Contacts</a>
+                        
+
+
+
    +
  1. +

    As with the link, a simple attribute makes this form AJAX-powered

    +
  2. +
+
+
+

A big advantage of the AJAX-based request that hx-boost uses (and the lack of + head processing that occurs) is that it + avoids what is known as a flash of unstyled content:

+
+
+
+
Flash Of Unstyled Content (FOUC)
+
+

A situation where a browser renders a web page before all the styling information is + available for the page. A FOUC causes a disconcerting momentary “flash” + of the unstyled content, which is then restyled + when all the style information is available. You will notice this as a flicker when + you move around the internet: text, + images and other content can “jump around” on the page as styles are + applied to it.

+
+
+
+
+

With hx-boost the site’s styling is already loaded before the new + content is retrieved, so there is no such flash of + unstyled content. This can make a “boosted” application feel both smoother and + also snappier in general.

+
+
+
+

Attribute Inheritance

+
+

Let’s expand on our previous example of a boosted link, and add a few more boosted + links alongside it. We add links + such that we have one to the /contacts page, the one to the + /settings page, and one to the /help page. All these + links are boosted and will behave in the manner that we have described above.

+
+
+

This feels a little redundant, doesn’t it? It seems silly to annotate all three links + with the hx-boost="true" attribute + right next to one another.

+
+
+
A Set of Boosted Links
+
+
<a href="/contacts" hx-boost="true">Contacts</a>
 <a href="/settings" hx-boost="true">Settings</a>
 <a href="/help" hx-boost="true">Help</a>
-
-
-
-

htmx offers a feature to help reduce this redundancy: attribute inheritance. With most attributes in htmx, if you -place it on a parent, the attribute will also apply to children elements. This is how Cascading Style Sheets work, and -that idea inspired htmx to adopt a similar “cascading htmx attributes” feature.

-
-
-

To avoid the redundancy in this example, let’s introduce a div element that encloses all the links and then “hoist” the -hx-boost attribute up to that parent div. This will let us remove the redundant hx-boost attributes but ensure all the links are -still boosted, inheriting that functionality from the parent div.

-
-
-

Note that any legal HTML element could be used here, we just use a div out of habit.

-
-
-
Boosting Links Via The Parent
-
-
<div hx-boost="true"> (1)
+                        
+
+
+

htmx offers a feature to help reduce this redundancy: attribute inheritance. With most + attributes in htmx, if you + place it on a parent, the attribute will also apply to children elements. This is how + Cascading Style Sheets work, and + that idea inspired htmx to adopt a similar “cascading htmx attributes” feature. +

+
+
+

To avoid the redundancy in this example, let’s introduce a div element + that encloses all the links and then “hoist” the + hx-boost attribute up to that parent div. This will let us remove + the redundant hx-boost attributes but ensure all the links are + still boosted, inheriting that functionality from the parent div. +

+
+
+

Note that any legal HTML element could be used here, we just use a div out of + habit.

+
+
+
Boosting Links Via The Parent
+
+
<div hx-boost="true"> (1)
     <a href="/contacts">Contacts</a>
     <a href="/settings">Settings</a>
     <a href="/help">Help</a>
 </div>
-
-
-
-
    -
  1. -

    The hx-boost has been moved to the parent div

    -
  2. -
-
-
-

Now we don’t have to put an hx-boost="true" on every link and, in fact, we can add more links alongside the -existing ones, and they, too, will be boosted, without us needing to explicitly annotate them.

-
-
-

That’s fine, but what if you have a link that you don’t want boosted within an element that has hx-boost="true" on -it? A good example of this situation is when a link is to a resource to be downloaded, such as a PDF. Downloading a -file can’t be handled well by an AJAX request, so you probably want that link to behave “normally”, issuing a full -page request for the PDF, which the browser will then offer to save as a file on the user’s local system.

-
-
-

To handle this situation, you would simply override the parent hx-boost value with hx-boost="false" on the -anchor tag that you didn’t want to be boosted:

-
-
-
Disabling Boosting
-
-
<div hx-boost="true"> (1)
+                        
+
+
+
    +
  1. +

    The hx-boost has been moved to the parent div

    +
  2. +
+
+
+

Now we don’t have to put an hx-boost="true" on every link and, in fact, we + can add more links alongside the + existing ones, and they, too, will be boosted, without us needing to explicitly annotate + them.

+
+
+

That’s fine, but what if you have a link that you don’t want boosted + within an element that has hx-boost="true" on + it? A good example of this situation is when a link is to a resource to be downloaded, such + as a PDF. Downloading a + file can’t be handled well by an AJAX request, so you probably want that link to + behave “normally”, issuing a full + page request for the PDF, which the browser will then offer to save as a file on the + user’s local system.

+
+
+

To handle this situation, you would simply override the parent hx-boost value + with hx-boost="false" on the + anchor tag that you didn’t want to be boosted:

+
+
+
Disabling Boosting
+
+
<div hx-boost="true"> (1)
     <a href="/contacts">Contacts</a>
     <a href="/settings">Settings</a>
     <a href="/help">Help</a>
     <a href="/help/documentation.pdf" hx-boost="false">Download Docs</a> (2)
 </div>
-
-
-
-
    -
  1. -

    The hx-boost is still on the parent div

    -
  2. -
  3. -

    The boosting behavior is overridden for this link

    -
  4. -
-
-
-

Here we have a new link to a documentation PDF that we wish to function like a regular link. We have added -hx-boost="false" to the link and this declaration will override the hx-boost="true" on the parent div, reverting -it to regular link behavior and, thus, allowing for the file download behavior that we want.

-
-
-
-

Progressive Enhancement

-
-

A nice aspect of hx-boost is that it is an example of progressive enhancement:

-
-
-
-
Progressive Enhancement
-
-

A software design philosophy that aims to provide as much essential content and functionality -to as many users as possible, while delivering a better experience to users with more advanced web browsers.

-
-
-
-
-

Consider the links in the example above. What would happen if someone did not have JavaScript enabled?

-
-
-

Nothing much!

-
-
-

The application would continue to work, but it would issue regular HTTP requests, rather than AJAX-based -HTTP requests. This means that your web application will work for the maximum number of users, with users of more modern -browsers (or users who have not turned off JavaScript) able to take advantage of the benefits of the AJAX-style navigation -that htmx offers, but other people will still able to use the app just fine.

-
-
-

Compare the behavior of htmx’s hx-boost attribute with a JavaScript heavy Single Page Application: such an application -often won’t function at all without JavaScript enabled. It is often very difficult to adopt a progressive enhancement -approach when you adopt an SPA framework.

-
-
-

This is not to say that every htmx feature offers progressive enhancement. It is certainly possible to build features that -do not offer a “No JS” fallback in htmx, and, in fact, many of the features we will build later in the book will fall -into this category. We will note when a feature is progressive enhancement friendly and when it is not.

-
-
-

Ultimately, it is up to you, the developer, to decide if the trade-offs of progressive enhancement (a more basic UX, -limited improvements over plain HTML) are worth the benefits for your application users.

-
-
-
-

Adding hx-boost to Contact.app

-
-

For the contact app we are building, we want this htmx “boost” behavior…​ well, everywhere.

-
-
-

Right? Why not?

-
-
-

How could we accomplish that?

-
-
-

Well, it’s pretty darned easy (and pretty common in htmx-powered web applications): we can just add hx-boost on the -body tag of our layout.html template, and we are done.

-
-
-
Boosting The Entire Contact.app
-
-
<html>
+                        
+
+
+
    +
  1. +

    The hx-boost is still on the parent div

    +
  2. +
  3. +

    The boosting behavior is overridden for this link

    +
  4. +
+
+
+

Here we have a new link to a documentation PDF that we wish to function like a regular link. + We have added + hx-boost="false" to the link and this declaration will override the + hx-boost="true" on the parent div, reverting + it to regular link behavior and, thus, allowing for the file download behavior that we want. +

+
+
+
+

Progressive Enhancement

+
+

A nice aspect of hx-boost is that it is an example of progressive + enhancement:

+
+
+
+
Progressive Enhancement
+
+

A software design philosophy that aims to provide as much essential content and + functionality + to as many users as possible, while delivering a better experience to users with + more advanced web browsers.

+
+
+
+
+

Consider the links in the example above. What would happen if someone did not have JavaScript + enabled?

+
+
+

Nothing much!

+
+
+

The application would continue to work, but it would issue regular HTTP requests, rather than + AJAX-based + HTTP requests. This means that your web application will work for the maximum number of + users, with users of more modern + browsers (or users who have not turned off JavaScript) able to take advantage of the + benefits of the AJAX-style navigation + that htmx offers, but other people will still able to use the app just fine.

+
+
+

Compare the behavior of htmx’s hx-boost attribute with a JavaScript heavy + Single Page Application: such an application + often won’t function at all without JavaScript enabled. It is often very + difficult to adopt a progressive enhancement + approach when you adopt an SPA framework.

+
+
+

This is not to say that every htmx feature offers progressive enhancement. It is + certainly possible to build features that + do not offer a “No JS” fallback in htmx, and, in fact, many of the features we + will build later in the book will fall + into this category. We will note when a feature is progressive enhancement friendly and when + it is not.

+
+
+

Ultimately, it is up to you, the developer, to decide if the trade-offs of progressive + enhancement (a more basic UX, + limited improvements over plain HTML) are worth the benefits for your application users.

+
+
+
+

Adding hx-boost to Contact.app

+
+

For the contact app we are building, we want this htmx “boost” + behavior…​ well, everywhere.

+
+
+

Right? Why not?

+
+
+

How could we accomplish that?

+
+
+

Well, it’s pretty darned easy (and pretty common in htmx-powered web applications): we + can just add hx-boost on the + body tag of our layout.html template, and we are done. +

+
+
+
Boosting The Entire Contact.app
+
+
<html>
 ...
 <body hx-boost="true">(1)
 ...
 </body>
 </html>
-
-
-
-
    -
  1. -

    All links and forms will be boosted now!

    -
  2. -
-
-
-

Now every link and form in our application will use AJAX by default, making it feel much snappier. Consider the -“New Contact” link that we created on the main page:

-
-
-
A Newly Boosted “Add Contact” Link
-
-
  <a href="/contacts/new">Add Contact</a>
-
-
-
-

Even though we haven’t touched anything on this link or on the server-side handling of the URL it targets, it will -now “just work” as a boosted link, using AJAX for a snappier user experience, including updating history, back button -support and so on. And, if JavaScript isn’t enabled, it will fall back to the normal link behavior.

-
-
-

All this with one, single htmx attribute.

-
-
-

hx-boost is more “magic” than other attributes in htmx, which generally are lower level and require a bit more explicit -annotation work, in order to specify exactly what you want htmx to do. In general, this is the design philosophy of htmx: -prefer explicit to implicit and obvious to “magic”. However, the hx-boost attribute is too useful to allow dogma to -override practicality, and so it is included as a feature in the library.

-
-
-
-
-

6.3. A Second Step: Deleting Contacts With an HTTP DELETE

-
-

For our next step with htmx, recall that Contact.app has a small form on the edit page of a contact that is -used to delete the contact:

-
-
-
Plain HTML Form To Delete A Contact
-
-
    <form action="/contacts/{{ contact.id }}/delete" method="post">
+                        
+
+
+
    +
  1. +

    All links and forms will be boosted now!

    +
  2. +
+
+
+

Now every link and form in our application will use AJAX by default, making it feel much + snappier. Consider the + “New Contact” link that we created on the main page:

+
+
+
A Newly Boosted “Add Contact” Link
+
+
  <a href="/contacts/new">Add Contact</a>
+
+
+
+

Even though we haven’t touched anything on this link or on the server-side handling of + the URL it targets, it will + now “just work” as a boosted link, using AJAX for a snappier user experience, + including updating history, back button + support and so on. And, if JavaScript isn’t enabled, it will fall back to the normal + link behavior.

+
+
+

All this with one, single htmx attribute.

+
+
+

hx-boost is more “magic” than other attributes in htmx, which + generally are lower level and require a bit more explicit + annotation work, in order to specify exactly what you want htmx to do. In general, this is + the design philosophy of htmx: + prefer explicit to implicit and obvious to “magic”. However, the + hx-boost attribute is too useful to allow dogma to + override practicality, and so it is included as a feature in the library.

+
+
+
+
+

6.3. A Second Step: Deleting Contacts With + an HTTP DELETE

+
+

For our next step with htmx, recall that Contact.app has a small form on the edit page of a + contact that is + used to delete the contact:

+
+
+
Plain HTML Form To Delete A Contact
+
+
    <form action="/contacts/{{ contact.id }}/delete" method="post">
         <button>Delete Contact</button>
     </form>
-
-
-
-

This form issued an HTTP POST to, for example, /contacts/42/delete, in order to delete the contact with the ID 42.

-
-
-

We mentioned previously that one of the annoying things about HTML is that you can’t issue an HTTP DELETE -(or PUT or PATCH) request directly, even though these are all part of HTTP and HTTP is obviously designed for -transferring HTML.

-
-
-

Thankfully, now, with htmx, we have a chance to rectify this situation.

-
-
-

The “right thing”, from a RESTful, resource oriented perspective is, rather than issuing an HTTP POST to -/contacts/42/delete, to issue an HTTP DELETE to /contacts/42. We want to delete the contact. The contact is -a resource. The URL for that resource is /contacts/42. So the ideal is a DELETE request to /contacts/42/.

-
-
-

Let’s update our application to do this by adding the htmx hx-delete attribute to the “Delete Contact” button:

-
-
-
An htmx Powered Button For Deleting A Contact
-
-
  <button hx-delete="/contacts/{{ contact.id }}">Delete Contact</button>
-
-
-
-

Now, when a user clicks this button, htmx will issue an HTTP DELETE request via AJAX to the URL for the contact -in question.

-
-
-

A couple of things to notice:

-
-
-
    -
  • -

    We no longer need a form tag to wrap the button, because the button itself carries the hypermedia action that -it performs directly on itself.

    -
  • -
  • -

    We no longer need to use the somewhat awkward "/contacts/{{ contact.id }}/delete" route, but can simply use the -"/contacts/{{ contact.id }} route, since we are issuing a DELETE. By using a DELETE we disambiguate between -a request intended to update the contact and a request intended to delete it, using the native HTTP tools available -for exactly this reason.

    -
  • -
-
-
-

Note that we have done something pretty magical here: we have turned this button into a hypermedia control. It is no -longer necessary that this button be placed within a larger form tag in order to trigger an HTTP request: it is a -stand-alone, and fully featured hypermedia control on its own. This is the crux of htmx, allowing any element to become -a hypermedia control and fully participate in the Hypermedia-Driven Application.

-
-
-

We should note that, unlike with the hx-boost examples above, this solution will not degrade gracefully. To make -this solution degrade gracefully, we would need to wrap the button in a form element and handle a POST on the server -side as well.

-
-
-

In the interest of keeping our application simple, we are going to omit that more elaborate solution.

-
-
-

Updating The Server Side

-
-

We have updated the client-side code (if HTML can be considered code) so it now issues a DELETE request to the appropriate -URL, but we still have some work to do. Since we updated both the route and the HTTP method we are using, we are going to -need to update the server-side implementation as well to handle this new HTTP Request.

-
-
-

Here is the original code for deleting a contact on the server side:

-
-
-
-
@app.route("/contacts/<contact_id>/delete", methods=["POST"])
+                    
+
+
+

This form issued an HTTP POST to, for example, /contacts/42/delete, in + order to delete the contact with the ID 42.

+
+
+

We mentioned previously that one of the annoying things about HTML is that you can’t issue + an HTTP DELETE + (or PUT or PATCH) request directly, even though these are all part of + HTTP and HTTP is obviously designed for + transferring HTML.

+
+
+

Thankfully, now, with htmx, we have a chance to rectify this situation.

+
+
+

The “right thing”, from a RESTful, resource oriented perspective is, rather than + issuing an HTTP POST to + /contacts/42/delete, to issue an HTTP DELETE to + /contacts/42. We want to delete the contact. The contact is + a resource. The URL for that resource is /contacts/42. So the ideal is a + DELETE request to /contacts/42/. +

+
+
+

Let’s update our application to do this by adding the htmx hx-delete attribute + to the “Delete Contact” button:

+
+
+
An htmx Powered Button For Deleting A Contact
+
+
  <button hx-delete="/contacts/{{ contact.id }}">Delete Contact</button>
+
+
+
+

Now, when a user clicks this button, htmx will issue an HTTP DELETE request via AJAX + to the URL for the contact + in question.

+
+
+

A couple of things to notice:

+
+
+
    +
  • +

    We no longer need a form tag to wrap the button, because the button itself + carries the hypermedia action that + it performs directly on itself.

    +
  • +
  • +

    We no longer need to use the somewhat awkward + "/contacts/{{ contact.id }}/delete" route, but can simply use the + "/contacts/{{ contact.id }} route, since we are issuing a + DELETE. By using a DELETE we disambiguate between + a request intended to update the contact and a request intended to delete it, using the + native HTTP tools available + for exactly this reason. +

    +
  • +
+
+
+

Note that we have done something pretty magical here: we have turned this button into a + hypermedia control. It is no + longer necessary that this button be placed within a larger form tag in order to + trigger an HTTP request: it is a + stand-alone, and fully featured hypermedia control on its own. This is the crux of htmx, + allowing any element to become + a hypermedia control and fully participate in the Hypermedia-Driven Application.

+
+
+

We should note that, unlike with the hx-boost examples above, this solution will + not degrade gracefully. To make + this solution degrade gracefully, we would need to wrap the button in a form element and handle + a POST on the server + side as well.

+
+
+

In the interest of keeping our application simple, we are going to omit that more elaborate + solution.

+
+
+

Updating The Server Side

+
+

We have updated the client-side code (if HTML can be considered code) so it now issues a + DELETE request to the appropriate + URL, but we still have some work to do. Since we updated both the route and the HTTP method + we are using, we are going to + need to update the server-side implementation as well to handle this new HTTP Request.

+
+
+

Here is the original code for deleting a contact on the server side:

+
+
+
+
@app.route("/contacts/<contact_id>/delete", methods=["POST"])
 def contacts_delete(contact_id=0):
     contact = Contact.find(contact_id)
     contact.delete()
     flash("Deleted Contact!")
     return redirect("/contacts")
-
-
-
-

We are going to have to make two changes to our handler: first we need to update the route for our handler to the new -location and then, secondly, we need to update the HTTP method we are using to delete contacts:

-
-
-
-
@app.route("/contacts/<contact_id>", methods=["DELETE"]) (1)
+                        
+
+
+

We are going to have to make two changes to our handler: first we need to update the route + for our handler to the new + location and then, secondly, we need to update the HTTP method we are using to delete + contacts:

+
+
+
+
@app.route("/contacts/<contact_id>", methods=["DELETE"]) (1)
 def contacts_delete(contact_id=0):
     contact = Contact.find(contact_id)
     contact.delete()
     flash("Deleted Contact!")
     return redirect("/contacts")
-
-
-
-
    -
  1. -

    An update path and method for the handler

    -
  2. -
-
-
-

Pretty simple, and much cleaner.

-
-
-
A Response Code Gotcha
-
-

Unfortunately, there is a problem with our updated handler: by default, in Flask the redirect() method responds with -a 302 Found HTTP Response Code.

-
-
-

According to the Mozilla Developer Network (MDN) web docs on the 302 Found -response, this means that the HTTP method of the request will be unchanged when the redirected HTTP request is issued.

-
-
-

We are now issuing a DELETE request with htmx and then being redirected to the /contacts path by flask. According to this -logic, that would mean that the redirected HTTP request would still be a DELETE method. This means that, as it stands, -the browser will issue a DELETE request to /contacts.

-
-
-

This is definitely not what we want: we would like the HTTP redirect to issue a GET request, slightly modifying the -Post/Redirect/Get behavior we discussed earlier to be a Delete/Redirect/Get.

-
-
-

Fortunately, there is a different response code, 303 See Other, -that does what we want: when a browser receives a 303 See Other redirect response, it will issue a GET to the new -location.

-
-
-

So we want to update our code to use the 303 response code in controller.

-
-
-

Thankfully, this is very easy: there is a second parameter to redirect() that takes the numeric response code you wish -to send.

-
-
-

Here is the updated code:

-
-
-
-
@app.route("/contacts/<contact_id>", methods=["DELETE"]) (1)
+                        
+
+
+
    +
  1. +

    An update path and method for the handler

    +
  2. +
+
+
+

Pretty simple, and much cleaner.

+
+
+
A Response Code Gotcha
+
+

Unfortunately, there is a problem with our updated handler: by default, in Flask the + redirect() method responds with + a 302 Found HTTP Response Code.

+
+
+

According to the Mozilla Developer Network (MDN) web docs on the 302 Found + response, this means that the HTTP method of the request will be + unchanged when the redirected HTTP request is issued.

+
+
+

We are now issuing a DELETE request with htmx and then being redirected to + the /contacts path by flask. According to this + logic, that would mean that the redirected HTTP request would still be a + DELETE method. This means that, as it stands, + the browser will issue a DELETE request to /contacts.

+
+
+

This is definitely not what we want: we would like the HTTP redirect to issue a + GET request, slightly modifying the + Post/Redirect/Get behavior we discussed earlier to be a Delete/Redirect/Get.

+
+
+

Fortunately, there is a different response code, 303 See Other, + that does what we want: when a browser receives a 303 See Other redirect + response, it will issue a GET to the new + location.

+
+
+

So we want to update our code to use the 303 response code in controller. +

+
+
+

Thankfully, this is very easy: there is a second parameter to redirect() + that takes the numeric response code you wish + to send.

+
+
+

Here is the updated code:

+
+
+
+
@app.route("/contacts/<contact_id>", methods=["DELETE"]) (1)
 def contacts_delete(contact_id=0):
     contact = Contact.find(contact_id)
     contact.delete()
     flash("Deleted Contact!")
     return redirect("/contacts", 303) (2)
-
-
-
-
    -
  1. -

    A slightly different path and method for the handler

    -
  2. -
  3. -

    The response code is now a 303

    -
  4. -
-
-
-

Now, when you want to remove a given contact, you can simply issue a DELETE to the same URL as you used to access the -contact in the first place.

-
-
-

This is a much more natural HTTP-based approach to deleting a resource!

-
-
-
-
-

Targeting The Right Element

-
-

We aren’t quite finished with our updated delete button yet, however. Recall that, by default, htmx “targets” the element -that triggers a request, and will place the HTML returned by the server inside that element. Right now, the “Delete Contact” -button is targeting itself.

-
-
-

That means that, since the redirect to the /contacts URL is going to re-render the entire contact list, we will end up -with that contact list placed inside the “Delete Contact” button.

-
-
-

Mis-targeting like this comes up from time to time when you are working with htmx and can lead to some pretty funny situations.

-
-
-

The fix for this is easy: add an explicit target to the button, and target the body element with the response:

-
-
-
A fixed htmx Powered Button For Deleting A Contact
-
-
  <button hx-delete="/contacts/{{ contact.id }}"
+                            
+
+
+
    +
  1. +

    A slightly different path and method for the handler

    +
  2. +
  3. +

    The response code is now a 303

    +
  4. +
+
+
+

Now, when you want to remove a given contact, you can simply issue a DELETE + to the same URL as you used to access the + contact in the first place.

+
+
+

This is a much more natural HTTP-based approach to deleting a resource!

+
+
+
+
+

Targeting The Right Element

+
+

We aren’t quite finished with our updated delete button yet, however. Recall that, by + default, htmx “targets” the element + that triggers a request, and will place the HTML returned by the server inside that element. + Right now, the “Delete Contact” + button is targeting itself.

+
+
+

That means that, since the redirect to the /contacts URL is going to re-render + the entire contact list, we will end up + with that contact list placed inside the “Delete Contact” button.

+
+
+

Mis-targeting like this comes up from time to time when you are working with htmx and can + lead to some pretty funny situations.

+
+
+

The fix for this is easy: add an explicit target to the button, and target the + body element with the response:

+
+
+
A fixed htmx Powered Button For Deleting A Contact
+
+
  <button hx-delete="/contacts/{{ contact.id }}"
           hx-target="body"> (1)
     Delete Contact
   </button>
-
-
-
-
    -
  1. -

    We have added an explicit target to the button now

    -
  2. -
-
-
-

Now our button behaves as expected: clicking on the button will issue an HTTP DELETE to the server against the URL for -the current contact, delete the contact and redirect back to the contact list page, with a nice flash message. We’ve -got everything working smoothly now.

-
-
-
-

Updating The Location Bar URL Properly

-
-

Well, almost.

-
-
-

If you click on the button you will notice that, despite the redirect, the URL in the location bar is -not correct. It still points to /contacts/{{ contact.id }}. That’s because we haven’t told htmx to update -the URL: it just issues the DELETE request and then updates the DOM with the response.

-
-
-

As we mentioned, boosting will naturally update the location bar for you, mimicking normal anchors and forms, but in -this case we are building a custom button hypermedia control because we want to issue a DELETE. So, in this case, we -need to let htmx know that we want the resulting URL from this request “pushed” into the location bar.

-
-
-

We can achieve this by adding the hx-push-url attribute with the value true to our button:

-
-
-
Deleting A Contact, Now With Proper Location Information
-
-
  <button hx-delete="/contacts/{{ contact.id }}"
+                        
+
+
+
    +
  1. +

    We have added an explicit target to the button now

    +
  2. +
+
+
+

Now our button behaves as expected: clicking on the button will issue an HTTP + DELETE to the server against the URL for + the current contact, delete the contact and redirect back to the contact list page, with a + nice flash message. We’ve + got everything working smoothly now.

+
+
+
+

Updating The Location Bar URL Properly

+
+

Well, almost.

+
+
+

If you click on the button you will notice that, despite the redirect, the URL in the + location bar is + not correct. It still points to /contacts/{{ contact.id }}. That’s + because we haven’t told htmx to update + the URL: it just issues the DELETE request and then updates the DOM with the + response.

+
+
+

As we mentioned, boosting will naturally update the location bar for you, mimicking normal + anchors and forms, but in + this case we are building a custom button hypermedia control because we want to issue a + DELETE. So, in this case, we + need to let htmx know that we want the resulting URL from this request “pushed” + into the location bar.

+
+
+

We can achieve this by adding the hx-push-url attribute with the value + true to our button:

+
+
+
Deleting A Contact, Now With Proper Location Information
+
+
  <button hx-delete="/contacts/{{ contact.id }}"
           hx-push-url="true" (1)
           hx-target="body">
     Delete Contact
   </button>
-
-
-
-
    -
  1. -

    We tell htmx to push the redirected URL up into the location bar

    -
  2. -
-
-
-

Now we are done.

-
-
-

We have a button that, all by itself, is able to issue a properly formatted HTTP DELETE request to -the correct URL, and the UI and location bar are all updated correctly. This was accomplished with three declarative -attributes placed directly on the button hx-delete, hx-target and hx-push-url.

-
-
-

This is definitely more work than the hx-boost change was, but it is explicit and easy to see what the button is doing -as a custom hypermedia control. And the resulting solution feels a lot cleaner as a total solution, taking advantage of -the built-in features of the web as a hypermedia system without any URL hacks.

-
-
-
-

One More Thing…​

-
-

There is one additional “bonus” feature we can add to our “Delete Contact” button: a confirmation dialog. Deleting -a contact is a destructive operation and as it stands right now, if the user inadvertently clicked the “Delete Contact” -button, the application would just delete that contact. Too bad, so sad for the user.

-
-
-

Fortunately htmx has an easy mechanism for adding a confirmation message on destructive operations like this: the -hx-confirm attribute. You can place this attribute on an element, with a message as its value, and the JavaScript -method confirm() will be called before a request is issued, which will show a simple confirmation dialog to the user -asking them to confirm the action. Very easy and a great way to prevent accidents.

-
-
-

Here is how we would add confirmation of the contact delete operation:

-
-
-
Confirming Deletion
-
-
  <button hx-delete="/contacts/{{ contact.id }}"
+                        
+
+
+
    +
  1. +

    We tell htmx to push the redirected URL up into the location bar

    +
  2. +
+
+
+

Now we are done.

+
+
+

We have a button that, all by itself, is able to issue a properly formatted HTTP + DELETE request to + the correct URL, and the UI and location bar are all updated correctly. This was + accomplished with three declarative + attributes placed directly on the button hx-delete, hx-target and + hx-push-url.

+
+
+

This is definitely more work than the hx-boost change was, but it is explicit + and easy to see what the button is doing + as a custom hypermedia control. And the resulting solution feels a lot cleaner as a total + solution, taking advantage of + the built-in features of the web as a hypermedia system without any URL hacks.

+
+
+
+

One More Thing…​

+
+

There is one additional “bonus” feature we can add to our “Delete + Contact” button: a confirmation dialog. Deleting + a contact is a destructive operation and as it stands right now, if the user inadvertently + clicked the “Delete Contact” + button, the application would just delete that contact. Too bad, so sad for the user.

+
+
+

Fortunately htmx has an easy mechanism for adding a confirmation message on destructive + operations like this: the + hx-confirm attribute. You can place this attribute on an element, with a + message as its value, and the JavaScript + method confirm() will be called before a request is issued, which will show a + simple confirmation dialog to the user + asking them to confirm the action. Very easy and a great way to prevent accidents. +

+
+
+

Here is how we would add confirmation of the contact delete operation:

+
+
+
Confirming Deletion
+
+
  <button hx-delete="/contacts/{{ contact.id }}"
           hx-push-url="true"
           hx-confirm="Are you sure you want to delete this contact?" (1)
           hx-target="body">
     Delete Contact
   </button>
-
-
-
-
    -
  1. -

    This message will be shown to the user, asking them to confirm the delete

    -
  2. -
-
-
-

Now, when someone clicks on the “Delete Contact” button, they will be presented with a prompt that asks “Are you sure -you want to delete this contact?” and they will have an opportunity to cancel if they clicked the button in error. Very -nice.

-
-
-

With this final change we now have a pretty solid “delete contact” mechanism: we are using the correct RESTful routes -and HTTP Methods, we are confirming the deletion, and we have removed a lot of the cruft that normal HTML imposes on us, -all while using declarative attributes in our HTML and staying firmly within the normal hypermedia model of the web.

-
-
-
-

Progressive Enhancement?

-
-

One thing to note about this solution, however, is that it is not a progressive enhancement to our web application: if -someone has disabled JavaScript then this “Delete Contact” button will no longer work. We could do additional work to keep -the older mechanism working in a JavaScript-disabled environment.

-
-
-

Progressive Enhancement can be a hot-button topic in web development, with lots of passionate opinions and perspectives. -Like nearly all JavaScript libraries, htmx makes it possible to create applications that do not function in the absence of -JavaScript. Retaining support for non-JavaScript clients requires additional work and complexity in your application. It -is important to determine exactly how important supporting non-JavaScript clients is before you begin using htmx, or any -other JavaScript framework, for improving your web applications.

-
-
-
-
-

6.4. Next Steps: Validating Contact Emails

-
-

Let’s move on to another improvement in our application: a big part of any web app is validating the data that is -submitted to the server: ensuring emails are correctly formatted and unique, numeric values are valid, dates are -acceptable, and so forth.

-
-
-

Currently, our application has a small amount of validation that is done entirely server-side and that displays an error -message when an error is detected.

-
-
-

We are not going to go into the details of how validation works in the model objects, but recall what -the code for updating a contact looks like from Chapter 4:

-
-
-
Server Side Validation On Contact Update
-
-
def contacts_edit_post(contact_id=0):
+                        
+
+
+
    +
  1. +

    This message will be shown to the user, asking them to confirm the delete

    +
  2. +
+
+
+

Now, when someone clicks on the “Delete Contact” button, they will be presented + with a prompt that asks “Are you sure + you want to delete this contact?” and they will have an opportunity to cancel if they + clicked the button in error. Very + nice.

+
+
+

With this final change we now have a pretty solid “delete contact” mechanism: we + are using the correct RESTful routes + and HTTP Methods, we are confirming the deletion, and we have removed a lot of the cruft + that normal HTML imposes on us, + all while using declarative attributes in our HTML and staying firmly within the normal + hypermedia model of the web.

+
+
+
+

Progressive Enhancement?

+
+

One thing to note about this solution, however, is that it is not a progressive + enhancement to our web application: if + someone has disabled JavaScript then this “Delete Contact” button will no longer + work. We could do additional work to keep + the older mechanism working in a JavaScript-disabled environment.

+
+
+

Progressive Enhancement can be a hot-button topic in web development, with lots of passionate + opinions and perspectives. + Like nearly all JavaScript libraries, htmx makes it possible to create applications that do + not function in the absence of + JavaScript. Retaining support for non-JavaScript clients requires additional work and + complexity in your application. It + is important to determine exactly how important supporting non-JavaScript clients is before + you begin using htmx, or any + other JavaScript framework, for improving your web applications.

+
+
+
+
+

6.4. Next Steps: Validating Contact Emails

+
+

Let’s move on to another improvement in our application: a big part of any web app is + validating the data that is + submitted to the server: ensuring emails are correctly formatted and unique, numeric values are + valid, dates are + acceptable, and so forth.

+
+
+

Currently, our application has a small amount of validation that is done entirely server-side and + that displays an error + message when an error is detected.

+
+
+

We are not going to go into the details of how validation works in the model objects, but recall + what + the code for updating a contact looks like from Chapter 4:

+
+
+
Server Side Validation On Contact Update
+
+
def contacts_edit_post(contact_id=0):
     c = Contact.find(contact_id)
     c.update(request.form['first_name'], request.form['last_name'], request.form['phone'], request.form['email'])
     if c.save(): (1)
@@ -6227,189 +10610,227 @@ the code for updating a contact looks like from Chapter 4:

return redirect("/contacts/" + str(contact_id)) else: return render_template("edit.html", contact=c) (2)
-
-
-
-
    -
  1. -

    We attempt to save the contact

    -
  2. -
  3. -

    If the save does not succeed we re-render the form to display error messages

    -
  4. -
-
-
-

So we attempt to save the contact, and, if the save() method returns true, we redirect to the contact’s detail page. -If the save() method does not return true, that indicates that there was a validation error and so, instead of redirecting -we re-render the HTML for editing the contact. This gives the user a chance to correct the errors, which are displayed -alongside the inputs.

-
-
-

Let’s take a look at the HTML for the email input:

-
-
-
Validation Error Messages
-
-
<p>
+                    
+
+
+
    +
  1. +

    We attempt to save the contact

    +
  2. +
  3. +

    If the save does not succeed we re-render the form to display error messages

    +
  4. +
+
+
+

So we attempt to save the contact, and, if the save() method returns true, we + redirect to the contact’s detail page. + If the save() method does not return true, that indicates that there was a + validation error and so, instead of redirecting + we re-render the HTML for editing the contact. This gives the user a chance to correct the + errors, which are displayed + alongside the inputs.

+
+
+

Let’s take a look at the HTML for the email input:

+
+
+
Validation Error Messages
+
+
<p>
     <label for="email">Email</label>
     <input name="email" id="email" type="text" placeholder="Email" value="{{ contact.email }}">
     <span class="error">{{ contact.errors['email'] }}</span>(1)
 </p>
-
-
-
-
    -
  1. -

    Display any errors associated with the email field

    -
  2. -
-
-
-

We have a label for the input, an input of type text and then a bit of HTML to display any error messages associated -with the email. When the template is rendered on the server, if there are errors associated with the contact’s email, they will -be displayed in this span, which will be highlighted red.

-
- -
-

The error message shown when a user attempts to save a contact with a duplicate email is "Email Must Be Unique":

-
-
-
-screenshot validation error -
-
Figure 3. Email Validation Error
-
-
-

All of this is done using plain HTML and using Web 1.0 techniques, and it works well.

-
-
-

However, as the application currently stands, there are two annoyances:

-
-
-
    -
  • -

    First, there is no email format validation: you can enter whatever characters you’d like as an email and, -as long as they are unique, the system will allow it

    -
  • -
  • -

    Second, if a user has entered a duplicate email, they will not find this fact out until they have filled in -all the fields because we only check the email’s uniqueness when all the data is submitted. This could be -quite annoying if the user was accidentally reentering a contact and had to put all the contact information in -before being made aware of this fact.

    -
  • -
-
-
-

Updating Our Input Type

-
-

For the first issue, we have a pure HTML mechanism for improving our application: HTML 5 supports inputs of -type email. All we need to do is switch our input from type text to type email, and the browser will -enforce that the value entered properly matches the email format:

-
-
-
Changing The Input To Type email
-
-
<p>
+                    
+
+
+
    +
  1. +

    Display any errors associated with the email field

    +
  2. +
+
+
+

We have a label for the input, an input of type text and then a bit of HTML to + display any error messages associated + with the email. When the template is rendered on the server, if there are errors associated with + the contact’s email, they will + be displayed in this span, which will be highlighted red.

+
+ +
+

The error message shown when a user attempts to save a contact with a duplicate email is "Email + Must Be Unique":

+
+
+
+ screenshot validation error +
+
Figure 3. Email Validation Error
+
+
+

All of this is done using plain HTML and using Web 1.0 techniques, and it works well.

+
+
+

However, as the application currently stands, there are two annoyances:

+
+
+
    +
  • +

    First, there is no email format validation: you can enter whatever characters you’d + like as an email and, + as long as they are unique, the system will allow it

    +
  • +
  • +

    Second, if a user has entered a duplicate email, they will not find this fact out until + they have filled in + all the fields because we only check the email’s uniqueness when all the data is + submitted. This could be + quite annoying if the user was accidentally reentering a contact and had to put all the + contact information in + before being made aware of this fact.

    +
  • +
+
+
+

Updating Our Input Type

+
+

For the first issue, we have a pure HTML mechanism for improving our application: HTML 5 + supports inputs of + type email. All we need to do is switch our input from type text + to type email, and the browser will + enforce that the value entered properly matches the email format:

+
+
+
Changing The Input To Type email
+
+
<p>
     <label for="email">Email</label>
     <input name="email" id="email" type="email" placeholder="Email" value="{{ contact.email }}"> (1)
     <span class="error">{{ contact.errors['email'] }}</span>
 </p>
-
-
-
-
    -
  1. -

    A simple change of the type attribute to email ensures that values entered are valid emails

    -
  2. -
-
-
-

With this change, when the user enters a value that isn’t a valid email, the browser will display an -error message asking for a properly formed email in that field.

-
-
-

So a simple single-attribute change done in pure HTML improves our validation and addresses the first problem -we noted.

-
- -
-
-

Inline Validation

-
-

While we have improved our validation experience a bit, the user must still submit the form to get any feedback -on duplicate emails. We can next use htmx to improve this user experience.

-
-
-

It would be better if the user were able to see a duplicate email error immediately after entering the email value. It -turns out that inputs fire a change event and, in fact, the change event is the default trigger for inputs in htmx. -So, putting this feature to work, we can implement the following behavior: when the user enters an email, immediately -issue a request to the server and validate that email, and render an error message if necessary.

-
-
-

Recall the current HTML for our email input:

-
-
-
The Initial Email Configuration
-
-
<p>
+                        
+
+
+
    +
  1. +

    A simple change of the type attribute to email ensures that + values entered are valid emails

    +
  2. +
+
+
+

With this change, when the user enters a value that isn’t a valid email, the browser + will display an + error message asking for a properly formed email in that field.

+
+
+

So a simple single-attribute change done in pure HTML improves our validation and addresses + the first problem + we noted.

+
+ +
+
+

Inline Validation

+
+

While we have improved our validation experience a bit, the user must still submit the form + to get any feedback + on duplicate emails. We can next use htmx to improve this user experience.

+
+
+

It would be better if the user were able to see a duplicate email error immediately after + entering the email value. It + turns out that inputs fire a change event and, in fact, the change + event is the default trigger for inputs in htmx. + So, putting this feature to work, we can implement the following behavior: when the user + enters an email, immediately + issue a request to the server and validate that email, and render an error message if + necessary.

+
+
+

Recall the current HTML for our email input:

+
+
+
The Initial Email Configuration
+
+
<p>
     <label for="email">Email</label>
     <input name="email" id="email" type="email" placeholder="Email" value="{{ contact.email }}"> (1)
     <span class="error">{{ contact.errors['email'] }}</span> (2)
 </p>
-
-
-
-
    -
  1. -

    This is the input that we want to have drive an HTTP request to validate the email

    -
  2. -
  3. -

    This is the span we want to put the error message, if any, into

    -
  4. -
-
-
-

So we want to add an hx-get attribute to this input. This will cause the input to issue an HTTP GET request to a -given URL to validate the email. We then want to target the error span following the input with any error message -returned from the server.

-
-
-

Let’s make those changes to our HTML:

-
-
-
Our Updated HTML
-
-
<p>
+                        
+
+
+
    +
  1. +

    This is the input that we want to have drive an HTTP request to validate the email +

    +
  2. +
  3. +

    This is the span we want to put the error message, if any, into

    +
  4. +
+
+
+

So we want to add an hx-get attribute to this input. This will cause the input + to issue an HTTP GET request to a + given URL to validate the email. We then want to target the error span following the input + with any error message + returned from the server.

+
+
+

Let’s make those changes to our HTML:

+
+
+
Our Updated HTML
+
+
<p>
     <label for="email">Email</label>
     <input name="email" id="email" type="email"
            hx-get="/contacts/{{ contact.id }}/email" (1)
@@ -6417,144 +10838,178 @@ returned from the server.

placeholder="Email" value="{{ contact.email }}"> (1) <span class="error">{{ contact.errors['email'] }}</span> </p>
-
-
-
-
    -
  1. -

    Issue an HTTP GET to the email endpoint for the contact

    -
  2. -
  3. -

    Target the next element with the class error on it

    -
  4. -
-
-
-

Note that in the hx-target attribute we are using a relative positional selector. This is a feature of htmx and -an extension to normal CSS. htmx supports prefixes that will find targets relative to the current element. Here -is a table of relative positional expressions available:

-
-
-
-
next
-
-

Scan forward in the DOM for the next matching element, e.g. next .error

-
-
previous
-
-

Scan backwards in the DOM for the closest previous matching element, e.g. previous .alert

-
-
closest
-
-

Scan the parents of this element for matching element, e.g. closest table

-
-
find
-
-

Scan the children of this element for matching element, e.g. find span

-
-
this
-
-

the current element is the target (default)

-
-
-
-
-

By using relative positional expressions we can avoid adding explicit ids to elements and take advantage of the local -structure of HTML.

-
-
-

So, with these two attributes in place, whenever someone changes the value of the input (remember, change is the -default trigger for inputs in htmx) an HTTP GET request will be issued to the given URL and, if there are any errors, they -will be loaded into the error span.

-
-
-
-

Validating Emails Server Side

-
-

Next, let’s look at the server-side implementation. We are going to add another end point, similar to our edit -end point in some ways: it is going to look up the contact based on the ID encoded in the URL. In this case, however, -we only want to update the email of the contact, and we obviously don’t want to save it! Instead, we will call the -validate() method on it.

-
-
-

That method will validate the email is unique and so forth. At that point we can return any errors associated with the -email directly, or the empty string if none exist.

-
-
-

Here is the code:

-
-
-
Our Email Validation End-Point
-
-
@app.route("/contacts/<contact_id>/email", methods=["GET"])
+                        
+
+
+
    +
  1. +

    Issue an HTTP GET to the email endpoint for the contact

    +
  2. +
  3. +

    Target the next element with the class error on it

    +
  4. +
+
+
+

Note that in the hx-target attribute we are using a relative positional + selector. This is a feature of htmx and + an extension to normal CSS. htmx supports prefixes that will find targets relative + to the current element. Here + is a table of relative positional expressions available:

+
+
+
+
next
+
+

Scan forward in the DOM for the next matching element, e.g. next .error +

+
+
previous
+
+

Scan backwards in the DOM for the closest previous matching element, e.g. + previous .alert

+
+
closest
+
+

Scan the parents of this element for matching element, e.g. + closest table

+
+
find
+
+

Scan the children of this element for matching element, e.g. find span +

+
+
this
+
+

the current element is the target (default)

+
+
+
+
+

By using relative positional expressions we can avoid adding explicit ids to elements and + take advantage of the local + structure of HTML.

+
+
+

So, with these two attributes in place, whenever someone changes the value of the input + (remember, change is the + default trigger for inputs in htmx) an HTTP GET request will be issued + to the given URL and, if there are any errors, they + will be loaded into the error span. +

+
+
+
+

Validating Emails Server Side

+
+

Next, let’s look at the server-side implementation. We are going to add another end + point, similar to our edit + end point in some ways: it is going to look up the contact based on the ID encoded in the + URL. In this case, however, + we only want to update the email of the contact, and we obviously don’t want to save + it! Instead, we will call the + validate() method on it. +

+
+
+

That method will validate the email is unique and so forth. At that point we can return any + errors associated with the + email directly, or the empty string if none exist.

+
+
+

Here is the code:

+
+
+
Our Email Validation End-Point
+
+
@app.route("/contacts/<contact_id>/email", methods=["GET"])
 def contacts_email_get(contact_id=0):
     c = Contact.find(contact_id) (1)
     c.email = request.args.get('email') (2)
     c.validate() (3)
     return c.errors.get('email') or "" (4)
-
-
-
-
    -
  1. -

    Look up the contact by id

    -
  2. -
  3. -

    Update its email (note that since this is a GET, we use the args property rather than the form property)

    -
  4. -
  5. -

    Validate the contact

    -
  6. -
  7. -

    Return a string, either the errors associated with the email field or, if there are none, the empty string

    -
  8. -
-
-
-

With this small bit of server-side code in place, we now have the following user experience: when a user enters an email -and tabs to the next input field, they are immediately notified if the email is already taken.

-
-
-

Note that the email validation is still done when the entire contact is submitted for an update, so there is no danger -of allowing duplicate email contacts to slip through: we have simply made it possible for users to catch this situation -earlier by use of htmx.

-
-
-

It is also worth noting that this particular email validation must be done on the server side: you cannot -determine that an email is unique across all contacts unless you have access to the data store of record. This is another -simplifying aspect of Hypermedia-Driven Applications: since validations are done server-side, you have access to all -the data you might need to do any sort of validation you’d like.

-
-
-

Here again we want to stress that this interaction is done entirely within the hypermedia model: we are using declarative -attributes and exchanging hypermedia with the server in a manner very similar to how links or forms work, but we have managed -to improve our user experience dramatically.

-
-
-
-

Taking The User Experience Further

-
-

Despite the fact that we haven’t added a lot of code here, we have a fairly sophisticated user interface, at -least when compared with plain HTML-based applications. However, if you have used more advanced Single Page Applications -you have probably seen the pattern where an email field (or a similar sort of input) is validated as you type.

-
-
-

This seems like the sort of interactivity that is only possible with a sophisticated, complex JavaScript framework, right?

-
-
-

Well, no.

-
-
-

It turns out that you can implement this functionality in htmx, using pure HTML attributes.

-
-
-

In fact, all we need to do is to change our trigger. Currently, we are using the default trigger for inputs, which is the -change event. To validate as the user types, we would want to capture the keyup event as well:

-
-
-
Triggering With keyup Events
-
-
<p>
+                        
+
+
+
    +
  1. +

    Look up the contact by id

    +
  2. +
  3. +

    Update its email (note that since this is a GET, we use the + args property rather than the form property)

    +
  4. +
  5. +

    Validate the contact

    +
  6. +
  7. +

    Return a string, either the errors associated with the email field or, if there are + none, the empty string

    +
  8. +
+
+
+

With this small bit of server-side code in place, we now have the following user experience: + when a user enters an email + and tabs to the next input field, they are immediately notified if the email is already + taken.

+
+
+

Note that the email validation is still done when the entire contact is submitted + for an update, so there is no danger + of allowing duplicate email contacts to slip through: we have simply made it possible for + users to catch this situation + earlier by use of htmx.

+
+
+

It is also worth noting that this particular email validation must be done on the + server side: you cannot + determine that an email is unique across all contacts unless you have access to the data + store of record. This is another + simplifying aspect of Hypermedia-Driven Applications: since validations are done + server-side, you have access to all + the data you might need to do any sort of validation you’d like.

+
+
+

Here again we want to stress that this interaction is done entirely within the hypermedia + model: we are using declarative + attributes and exchanging hypermedia with the server in a manner very similar to how links + or forms work, but we have managed + to improve our user experience dramatically.

+
+
+
+

Taking The User Experience Further

+
+

Despite the fact that we haven’t added a lot of code here, we have a fairly + sophisticated user interface, at + least when compared with plain HTML-based applications. However, if you have used more + advanced Single Page Applications + you have probably seen the pattern where an email field (or a similar sort of input) is + validated as you type.

+
+
+

This seems like the sort of interactivity that is only possible with a sophisticated, complex + JavaScript framework, right?

+
+
+

Well, no.

+
+
+

It turns out that you can implement this functionality in htmx, using pure HTML attributes. +

+
+
+

In fact, all we need to do is to change our trigger. Currently, we are using the default + trigger for inputs, which is the + change event. To validate as the user types, we would want to capture the + keyup event as well: +

+
+
+
Triggering With keyup Events
+
+
<p>
     <label for="email">Email</label>
     <input name="email" id="email" type="email"
            hx-get="/contacts/{{ contact.id }}/email"
@@ -6563,43 +11018,52 @@ you have probably seen the pattern where an email field (or a similar sort of in
            placeholder="Email" value="{{ contact.email }}">
     <span class="error">{{ contact.errors['email'] }}</span>
 </p>
-
-
-
-
    -
  1. -

    An explicit trigger has been declared, and it triggers on both the change and keyup events

    -
  2. -
-
-
-

With this tiny change, every time a user types a character we will issue a request and validate the email. Simple as.

-
-
-
-

Debouncing Our Validation Requests

-
-

Simple as, yes, but probably not what we want: issuing a new request on every key up event would be very wasteful -and could potentially overwhelm your server. What we want instead is only issue the request if the user has paused for -a small amount of time. This is called “debouncing” the input, where requests are delayed until things have “settled down”.

-
-
-

htmx supports a delay modifier for triggers that allows you to debounce a request by adding a delay before the request -is sent. If another event of the same kind appears within that interval, htmx will not issue the request and will reset -the timer.

-
-
-

This turns out to be exactly what we want for our email input: if the user is busy typing in an email we won’t interrupt them, -but as soon as they pause or leave the field, we’ll issue a request.

-
-
-

Let’s add a delay of 200 milliseconds to the keyup trigger, which is long enough to detect that the user has stopped -typing.:

-
-
-
Debouncing the keyup Event
-
-
<p>
+                        
+
+
+
    +
  1. +

    An explicit trigger has been declared, and it triggers on both the + change and keyup events

    +
  2. +
+
+
+

With this tiny change, every time a user types a character we will issue a request and + validate the email. Simple as.

+
+
+
+

Debouncing Our Validation Requests

+
+

Simple as, yes, but probably not what we want: issuing a new request on every key up event + would be very wasteful + and could potentially overwhelm your server. What we want instead is only issue the request + if the user has paused for + a small amount of time. This is called “debouncing” the input, where requests + are delayed until things have “settled down”.

+
+
+

htmx supports a delay modifier for triggers that allows you to debounce a + request by adding a delay before the request + is sent. If another event of the same kind appears within that interval, htmx will not issue + the request and will reset + the timer.

+
+
+

This turns out to be exactly what we want for our email input: if the user is busy typing in + an email we won’t interrupt them, + but as soon as they pause or leave the field, we’ll issue a request.

+
+
+

Let’s add a delay of 200 milliseconds to the keyup trigger, which is long + enough to detect that the user has stopped + typing.:

+
+
+
Debouncing the keyup Event
+
+
<p>
     <label for="email">Email</label>
     <input name="email" id="email" type="email"
            hx-get="/contacts/{{ contact.id }}/email"
@@ -6608,39 +11072,45 @@ typing.:

placeholder="Email" value="{{ contact.email }}"> <span class="error">{{ contact.errors['email'] }}</span> </p>
-
-
-
-
    -
  1. -

    We debounce the keyup event by adding a delay modifier

    -
  2. -
-
-
-

Now we no longer issue a stream of validation requests as the user types. Instead, we wait until the user pauses for -a bit and then issue the request. Much better for our server, and still a great user experience.

-
-
-
-

Ignoring Non-Mutating Keys

-
-

There is one last issue we should address with the keyup event: as it stands we will issue a request no matter which keys -are pressed, even if they are keys that have no effect on the value of the input, such as arrow keys. It would be better -if there were a way to only issue a request if the input value has changed.

-
-
-

And it turns out that htmx has support for that exact pattern, by using the changed modifier for events. (Not to be -confused with the change event triggered by the DOM on input elements.)

-
-
-

By adding changed to our keyup trigger, the input will not issue validation requests unless the keyup event actually -updates the inputs value:

-
-
-
Only Sending Requests When The Input Value Changes
-
-
<p>
+                        
+
+
+
    +
  1. +

    We debounce the keyup event by adding a delay modifier

    +
  2. +
+
+
+

Now we no longer issue a stream of validation requests as the user types. Instead, we wait + until the user pauses for + a bit and then issue the request. Much better for our server, and still a great user + experience.

+
+
+
+

Ignoring Non-Mutating Keys

+
+

There is one last issue we should address with the keyup event: as it stands we will issue a + request no matter which keys + are pressed, even if they are keys that have no effect on the value of the input, such as + arrow keys. It would be better + if there were a way to only issue a request if the input value has changed.

+
+
+

And it turns out that htmx has support for that exact pattern, by using the + changed modifier for events. (Not to be + confused with the change event triggered by the DOM on input elements.)

+
+
+

By adding changed to our keyup trigger, the input will not issue + validation requests unless the keyup event actually + updates the inputs value:

+
+
+
Only Sending Requests When The Input Value Changes
+
+
<p>
     <label for="email">Email</label>
     <input name="email" id="email" type="email"
            hx-get="/contacts/{{ contact.id }}/email"
@@ -6649,74 +11119,91 @@ updates the inputs value:

placeholder="Email" value="{{ contact.email }}"> <span class="error">{{ contact.errors['email'] }}</span> </p>
-
-
-
-
    -
  1. -

    We do away with pointless requests by only issuing them when the input’s value has actually changed

    -
  2. -
-
-
-

That’s some pretty good-looking and powerful HTML, providing an experience that most developers would think requires -a complicated client-side solution.

-
-
-

With a total of three attributes and a simple new server-side end point, we have added a fairly sophisticated user -experience to our web application. Even better, any email validation rules we add on the server side will -automatically just work using this model: because we are using hypermedia as our communication mechanism there is no -need to keep a client-side and server-side model in sync with one another.

-
-
-

A great demonstration of the power of the hypermedia architecture.

-
-
-
-
-

6.5. Another Application Improvement: Paging

-
-

Let’s move on from the contact editing page for a bit and improve the root page of the application, found -at the /contacts path and rendering the index.html template.

-
-
-

Currently, Contact.app does not support paging: if there are 10,000 contacts in the database we will show -all 10,000 contacts on the root page. Showing so much data can bog a browser (and a server) down, so most web applications -adopt a concept of “paging” to deal with data sets this large, where only one “page” of a smaller number of items is -shown, with the ability to navigate around the pages in the data set.

-
-
-

Let’s fix our application, so that we only show ten contacts at a time with a “Next” and “Previous” link if there are more -than 10 contacts in the contact database.

-
-
-

The first change we will need to make is to add a simple paging widget to our index.html template.

-
-
-

We will conditionally include two links:

-
-
-
    -
  • -

    If we are beyond the “first” page, we will include a link to the previous page

    -
  • -
  • -

    If there are ten contacts in the current result set, we will include a link to the next page

    -
  • -
-
-
-

This isn’t a perfect paging widget: ideally we’d show the number of pages and offer the ability to do more -specific page navigation, and there is the possibility that the next page might have 0 results in it since -we aren’t checking the total results count, but it will do for now for our simple application.

-
-
-

Let’s look at the jinja template code for this in index.html

-
-
-
Adding Paging Widgets To Our List of Contacts
-
-
<div>
+                        
+
+
+
    +
  1. +

    We do away with pointless requests by only issuing them when the input’s value + has actually changed

    +
  2. +
+
+
+

That’s some pretty good-looking and powerful HTML, providing an experience that most + developers would think requires + a complicated client-side solution.

+
+
+

With a total of three attributes and a simple new server-side end point, we have added a + fairly sophisticated user + experience to our web application. Even better, any email validation rules we add on the + server side will + automatically just work using this model: because we are using hypermedia as our + communication mechanism there is no + need to keep a client-side and server-side model in sync with one another. +

+
+
+

A great demonstration of the power of the hypermedia architecture.

+
+
+
+
+

6.5. Another Application Improvement: Paging

+
+

Let’s move on from the contact editing page for a bit and improve the root page of the + application, found + at the /contacts path and rendering the index.html template.

+
+
+

Currently, Contact.app does not support paging: if there are 10,000 contacts in the database we + will show + all 10,000 contacts on the root page. Showing so much data can bog a browser (and a server) + down, so most web applications + adopt a concept of “paging” to deal with data sets this large, where only one + “page” of a smaller number of items is + shown, with the ability to navigate around the pages in the data set.

+
+
+

Let’s fix our application, so that we only show ten contacts at a time with a + “Next” and “Previous” link if there are more + than 10 contacts in the contact database.

+
+
+

The first change we will need to make is to add a simple paging widget to our + index.html template.

+
+
+

We will conditionally include two links:

+
+
+
    +
  • +

    If we are beyond the “first” page, we will include a link to the previous + page

    +
  • +
  • +

    If there are ten contacts in the current result set, we will include a link to the next + page

    +
  • +
+
+
+

This isn’t a perfect paging widget: ideally we’d show the number of pages and offer + the ability to do more + specific page navigation, and there is the possibility that the next page might have 0 results + in it since + we aren’t checking the total results count, but it will do for now for our simple + application.

+
+
+

Let’s look at the jinja template code for this in index.html

+
+
+
Adding Paging Widgets To Our List of Contacts
+
+
<div>
     <span style="float: right"> (1)
         {% if page > 1 %}
           <a href="/contacts?page={{ page - 1 }}">Previous</a> (2)
@@ -6726,37 +11213,44 @@ we aren’t checking the total results count, but it will do for now for our
         {% endif %}
     </span>
 </div>
-
-
-
-
    -
  1. -

    Include a new div under the table to hold our navigation links

    -
  2. -
  3. -

    If we are beyond page 1, include an anchor tag with the page decremented by one

    -
  4. -
  5. -

    If there are 10 contacts in the current page, include an anchor tag linking to the next page by incrementing it by one

    -
  6. -
-
-
-

Note that here we are using a special jinja filter syntax contacts|length to compute the length of the contacts -list. The details of this filter syntax is beyond the scope of this book, but in this case you can think of it as -invoking the contacts.length property and then comparing that with 10.

-
-
-

Now that we have these links in place to support paging, let’s address the server-side implementation of paging.

-
-
-

We are using the page request parameter to encode the paging state of the UI. So, in our handler, we need to look for -that page parameter and pass that through to our model, as an integer, so the model knows which page of contacts to return:

-
-
-
Adding Paging To Our Request Handler
-
-
@app.route("/contacts")
+                    
+
+
+
    +
  1. +

    Include a new div under the table to hold our navigation links

    +
  2. +
  3. +

    If we are beyond page 1, include an anchor tag with the page decremented by one

    +
  4. +
  5. +

    If there are 10 contacts in the current page, include an anchor tag linking to the next + page by incrementing it by one

    +
  6. +
+
+
+

Note that here we are using a special jinja filter syntax contacts|length to compute + the length of the contacts + list. The details of this filter syntax is beyond the scope of this book, but in this case you + can think of it as + invoking the contacts.length property and then comparing that with 10. +

+
+
+

Now that we have these links in place to support paging, let’s address the server-side + implementation of paging.

+
+
+

We are using the page request parameter to encode the paging state of the UI. So, in + our handler, we need to look for + that page parameter and pass that through to our model, as an integer, so the model + knows which page of contacts to return:

+
+
+
Adding Paging To Our Request Handler
+
+
@app.route("/contacts")
 def contacts():
     search = request.args.get("q")
     page = int(request.args.get("page", 1)) (1)
@@ -6765,72 +11259,88 @@ def contacts():
     else:
         contacts_set = Contact.all(page) (2)
     return render_template("index.html", contacts=contacts_set, page=page)
-
-
-
-
    -
  1. -

    Resolve the page parameter, defaulting to page 1 if no page is passed in

    -
  2. -
  3. -

    Pass the page through to the model when loading all contacts so it knows which page of 10 contacts to -return

    -
  4. -
-
-
-

This is fairly straightforward: we just need to get another parameter, like the q parameter we passed in for -searching contacts earlier, convert it to an integer and then pass it through to the Contact model, so it -knows which page to return.

-
-
-

And, with that small change, we are done: we now have a very basic paging mechanism for our web application.

-
-
-

And, believe it or not, it is already using AJAX, thanks to our use of hx-boost in the application. Easy.

-
-
-

Click To Load

-
-

This paging mechanism is fine for a basic web application, and it is used extensively on the internet. But it has some -drawbacks associated with it: every time you click the “Next” or “Previous” buttons you get a whole new page of contacts -and lose any context you had on the previous page.

-
-
-

Sometimes a more advanced paging UI pattern might be better. Maybe, rather than loading in a new page of elements and -replacing the current elements, it would be nicer to append the next page of elements inline, after the current -elements.

-
-
-

This is the common “click to load” UX pattern, found in more advanced web applications.

-
-
-
-screenshot click to load.txt -
-
Figure 4. A Click To Load UI
-
-
-

Here, you have a button that you can click, and it will load the next set of contacts directly into the page, rather -than “paging” to the next page. This allows you to keep the current contacts “in context” visually on the page, but -still progress through them as you would in a normal, paged user interface.

-
-
-

Let’s see how we can implement this UX pattern in htmx.

-
-
-

It’s actually surprisingly simple: we can just take the existing “Next” link and repurpose it a bit using -nothing but a few htmx attributes!

-
-
-

We want to have a button that, when clicked, appends the rows from the next page of contacts to the current, -exiting table, rather than re-rendering the whole table. This can be achieved by adding a new row to our table -that has just such a button in it:

-
-
-
Changing To “Click To Load”
-
-
        <tbody>
+                    
+
+
+
    +
  1. +

    Resolve the page parameter, defaulting to page 1 if no page is passed in

    +
  2. +
  3. +

    Pass the page through to the model when loading all contacts so it knows which page of 10 + contacts to + return

    +
  4. +
+
+
+

This is fairly straightforward: we just need to get another parameter, like the q + parameter we passed in for + searching contacts earlier, convert it to an integer and then pass it through to the + Contact model, so it + knows which page to return.

+
+
+

And, with that small change, we are done: we now have a very basic paging mechanism for our web + application.

+
+
+

And, believe it or not, it is already using AJAX, thanks to our use of hx-boost in + the application. Easy.

+
+
+

Click To Load

+
+

This paging mechanism is fine for a basic web application, and it is used extensively on the + internet. But it has some + drawbacks associated with it: every time you click the “Next” or + “Previous” buttons you get a whole new page of contacts + and lose any context you had on the previous page.

+
+
+

Sometimes a more advanced paging UI pattern might be better. Maybe, rather than loading in a + new page of elements and + replacing the current elements, it would be nicer to append the next page of elements + inline, after the current + elements.

+
+
+

This is the common “click to load” UX pattern, found in more advanced web + applications.

+
+
+
+ screenshot click to load.txt +
+
Figure 4. A Click To Load UI
+
+
+

Here, you have a button that you can click, and it will load the next set of contacts + directly into the page, rather + than “paging” to the next page. This allows you to keep the current contacts + “in context” visually on the page, but + still progress through them as you would in a normal, paged user interface.

+
+
+

Let’s see how we can implement this UX pattern in htmx.

+
+
+

It’s actually surprisingly simple: we can just take the existing “Next” + link and repurpose it a bit using + nothing but a few htmx attributes!

+
+
+

We want to have a button that, when clicked, appends the rows from the next page of contacts + to the current, + exiting table, rather than re-rendering the whole table. This can be achieved by adding a + new row to our table + that has just such a button in it:

+
+
+
Changing To “Click To Load”
+
+
        <tbody>
         {% for contact in contacts %}
             <tr>
                 <td>{{ contact.first }}</td>
@@ -6853,79 +11363,98 @@ that has just such a button in it:

</tr> {% endif %} </tbody>
-
-
-
-
    -
  1. -

    Only show “Load More” if there are 10 contact results in the current page

    -
  2. -
  3. -

    Target the closest enclosing row

    -
  4. -
  5. -

    Replace the entire row with the response from the server

    -
  6. -
  7. -

    Select out the table rows from the response

    -
  8. -
-
-
-

Let’s go through each attribute in detail here.

-
-
-

First, we are using hx-target to target the “closest” tr element, that is, the closest parent table row.

-
-
-

Second, we want to replace this entire row with whatever content comes back from the server.

-
-
-

Third, we want to yank out only the tr elements in the response. We are replacing this tr element with a new set -of tr elements, which will have additional contact information in them, as well as, if necessary, a new “Load More” -button that points to the next next page. To do this, we use a CSS selector tbody > tr to ensure we only pull -out the rows in the body of the table in the response. This avoids including rows in the table header, for example.

-
-
-

Finally, we issue an HTTP GET to the url that will serve the next page of contacts, which looks just like the “Next” -link from above.

-
-
-

Somewhat surprisingly, no server-side changes are necessary for this new functionality. This is because of the flexibility -that htmx gives you with respect to how it processes server responses.

-
-
-

So, four attributes, and we now have a sophisticated “Click To Load” UX, via htmx.

-
-
-
-

Infinite Scroll

-
-

Another common pattern for dealing with large sets of things is known as the “Infinite Scroll” pattern. In this pattern, -as the last item of a list or table of elements is scrolled into view, more elements are loaded and appended to the list -or table.

-
-
-

Now, this behavior makes more sense in situations where a user is exploring a category or series of social media posts, rather -than in the context of a contact application. However, for completeness, and to just show off what you can do with -htmx, we will show how to implement this pattern as well.

-
-
-

It turns out that we can repurpose the “Click To Load” code to implement this new pattern quite easily: if you think -about it for a moment, infinite scroll is really just the “Click To Load” logic, but rather than loading when a click -event occurs, we want to load when an element is “revealed” in the view portal of the browser.

-
-
-

As luck would have it, htmx offers a synthetic (non-standard) DOM event, revealed that can be used in tandem -with the hx-trigger attribute, to trigger a request when, well, when an element is revealed.

-
-
-

So let’s convert our button to a span and take advantage of this event:

-
-
-
Changing To “Infinite Scroll”
-
-
{% if contacts|length == 10 %} (1)
+                        
+
+
+
    +
  1. +

    Only show “Load More” if there are 10 contact results in the current page +

    +
  2. +
  3. +

    Target the closest enclosing row

    +
  4. +
  5. +

    Replace the entire row with the response from the server

    +
  6. +
  7. +

    Select out the table rows from the response

    +
  8. +
+
+
+

Let’s go through each attribute in detail here.

+
+
+

First, we are using hx-target to target the “closest” + tr element, that is, the closest parent table row.

+
+
+

Second, we want to replace this entire row with whatever content comes back from the + server.

+
+
+

Third, we want to yank out only the tr elements in the response. We are + replacing this tr element with a new set + of tr elements, which will have additional contact information in them, as well + as, if necessary, a new “Load More” + button that points to the next next page. To do this, we use a CSS selector + tbody > tr to ensure we only pull + out the rows in the body of the table in the response. This avoids including rows in the + table header, for example.

+
+
+

Finally, we issue an HTTP GET to the url that will serve the next page of + contacts, which looks just like the “Next” + link from above.

+
+
+

Somewhat surprisingly, no server-side changes are necessary for this new functionality. This + is because of the flexibility + that htmx gives you with respect to how it processes server responses.

+
+
+

So, four attributes, and we now have a sophisticated “Click To Load” UX, via + htmx.

+
+
+
+

Infinite Scroll

+
+

Another common pattern for dealing with large sets of things is known as the “Infinite + Scroll” pattern. In this pattern, + as the last item of a list or table of elements is scrolled into view, more elements are + loaded and appended to the list + or table.

+
+
+

Now, this behavior makes more sense in situations where a user is exploring a category or + series of social media posts, rather + than in the context of a contact application. However, for completeness, and to just show + off what you can do with + htmx, we will show how to implement this pattern as well.

+
+
+

It turns out that we can repurpose the “Click To Load” code to implement this new + pattern quite easily: if you think + about it for a moment, infinite scroll is really just the “Click To Load” logic, + but rather than loading when a click + event occurs, we want to load when an element is “revealed” in the view portal + of the browser.

+
+
+

As luck would have it, htmx offers a synthetic (non-standard) DOM event, + revealed that can be used in tandem + with the hx-trigger attribute, to trigger a request when, well, when an element + is revealed.

+
+
+

So let’s convert our button to a span and take advantage of this event:

+
+
+
Changing To “Infinite Scroll”
+
+
{% if contacts|length == 10 %} (1)
     <tr>
         <td colspan="5" style="text-align: center">
             <span<1>hx-target="closest tr"
@@ -6936,182 +11465,229 @@ with the hx-trigger attribute, to trigger a request when, well, whe
         </td>
     </tr>
 {% endif %}
-
-
-
-
    -
  1. -

    We have converted our element from a button to a span, since the user will not be clicking on it

    -
  2. -
  3. -

    We trigger the request when the element is revealed, that is when it comes into view in the portal

    -
  4. -
-
-
-

All we needed to do to convert from “Click to Load” to “Infinite Scroll” was to update our element to be -a span and then add the revealed event trigger.

-
-
-

The fact that switching to infinite scroll was so easy shows how well htmx generalizes HTML: just a few attributes allow -us to dramatically expand what we can achieve in the hypermedia.

-
-
-

And, again, we note that we are doing all this within the original, RESTful model of the web: despite all this new -behavior, we are still exchanging hypermedia with the server, no JSON API response to be seen.

-
-
-

As the web was designed.

-
-
-
-
-
-
-

7. More htmx Patterns

-
-
- -
-

So far so good with Contact.app: we have a nice little web application with some significant improvements over a plain -HTML-based application. We’ve added a proper “Delete Contact” button, done some dynamic validation of input and looked -as some different approaches to add paging to the application. As we have said, many web developers would expect that -we would have needed to add a lot of JavaScript-based scripting to get these features, but we’ve done it all in relatively -pure HTML, using only htmx attributes.

-
-
-

We will eventually add some client-side scripting to our application: hypermedia is powerful, but it isn’t all powerful and -sometimes scripting might be the best (or only) way to achieve a given goal. For now, however, let’s see what we can accomplish -with hypermedia.

-
-
-

The first advanced htmx feature we will create is known as the “Active Search” pattern. Active Search is when, as a -user types text into a search box, the results of that search are dynamically shown. This pattern was made popular -when Google adopted it for search results, and many applications now implement it.

-
-
-

To implement Active Search, we are going to use techniques closely related to the way we did email validation in the -previous chapter. If you think about it, the two features are similar in many ways: in both cases we want to issue -a request as the user types into an input and then update some other element with a response. The server-side implementations -will, of course, be very different, but the front end code will look fairly similar, a testament to how general the “issue -a request on an event and replace something on the screen” approach that htmx takes really is.

-
-
-

Our Current Search UI

-
-

Let’s recall what the current search field in our application currently looks like:

-
-
-
Our Search Form
-
-
<form action="/contacts" method="get" class="tool-bar">
+                        
+
+
+
    +
  1. +

    We have converted our element from a button to a span, since the user will not be + clicking on it

    +
  2. +
  3. +

    We trigger the request when the element is revealed, that is when it comes into view + in the portal

    +
  4. +
+
+
+

All we needed to do to convert from “Click to Load” to “Infinite + Scroll” was to update our element to be + a span and then add the revealed event trigger.

+
+
+

The fact that switching to infinite scroll was so easy shows how well htmx generalizes HTML: + just a few attributes allow + us to dramatically expand what we can achieve in the hypermedia.

+
+
+

And, again, we note that we are doing all this within the original, RESTful model of the web: + despite all this new + behavior, we are still exchanging hypermedia with the server, no JSON API response to be + seen.

+
+
+

As the web was designed.

+
+
+
+
+
+
+

7. More htmx Patterns

+
+
+ +
+

So far so good with Contact.app: we have a nice little web application with some significant + improvements over a plain + HTML-based application. We’ve added a proper “Delete Contact” button, done + some dynamic validation of input and looked + as some different approaches to add paging to the application. As we have said, many web + developers would expect that + we would have needed to add a lot of JavaScript-based scripting to get these features, but + we’ve done it all in relatively + pure HTML, using only htmx attributes.

+
+
+

We will eventually add some client-side scripting to our application: hypermedia is + powerful, but it isn’t all powerful and + sometimes scripting might be the best (or only) way to achieve a given goal. For now, however, + let’s see what we can accomplish + with hypermedia.

+
+
+

The first advanced htmx feature we will create is known as the “Active Search” + pattern. Active Search is when, as a + user types text into a search box, the results of that search are dynamically shown. This + pattern was made popular + when Google adopted it for search results, and many applications now implement it.

+
+
+

To implement Active Search, we are going to use techniques closely related to the way we did + email validation in the + previous chapter. If you think about it, the two features are similar in many ways: in both + cases we want to issue + a request as the user types into an input and then update some other element with a response. + The server-side implementations + will, of course, be very different, but the front end code will look fairly similar, a testament + to how general the “issue + a request on an event and replace something on the screen” approach that htmx takes really + is.

+
+
+

Our Current Search UI

+
+

Let’s recall what the current search field in our application currently looks like:

+
+
+
Our Search Form
+
+
<form action="/contacts" method="get" class="tool-bar">
     <label for="search">Search Term</label>
     <input id="search" type="search" name="q" value="{{ request.args.get('q') or '' }}"/> (1)
     <input type="submit" value="Search"/>
 </form>
-
-
-
-
    -
  1. -

    The q or “query” parameter our client-side code uses to search

    -
  2. -
-
-
-

Recall that we have some server-side code that looks for the q parameter and, if it is present, searches the contacts -for that term.

-
-
-

As it stands right now, the user must hit enter when the search input is focused, or click the “Search” button. Both -of these events will trigger a submit event on the form, causing it to issue an HTTP GET and re-rendering the whole -page.

-
-
-

Currently, thanks to hx-boost, the form will use an AJAX request for this GET, but we currently don’t get that nice -search-as-you-type behavior that we want.

-
-
-
- -
-

To add active search behavior, we will need to add a few htmx attributes to the search input. We will leave the current -form as it is, with an action and method, so that, in case a user does not have JavaScript enabled, the normal -search behavior continues to work. This will make our “Active Search” improvement a nice “progressive enhancement”.

-
-
-

So, in addition to the regular form behavior, we also want to issue an HTTP GET request when a key up occurs. We want -to issue this request to the same URL as the normal form submission. Finally, we only want to do this after a small -pause in typing has occurred.

-
-
-

As we said, this functionality is very similar to what we needed for email validation isn’t it? We can, in fact copy -the hx-trigger attribute directly from our email validation example, with its small 200-millisecond delay, to allow a -user to stop typing before a request is triggered.

-
-
-

Again, a great example of how common patterns come up again and again when you are using htmx.

-
-
-
Adding Active Search Behavior
-
-
<form action="/contacts" method="get" class="tool-bar">
+                        
+
+
+
    +
  1. +

    The q or “query” parameter our client-side code uses to + search

    +
  2. +
+
+
+

Recall that we have some server-side code that looks for the q parameter and, if + it is present, searches the contacts + for that term.

+
+
+

As it stands right now, the user must hit enter when the search input is focused, or click + the “Search” button. Both + of these events will trigger a submit event on the form, causing it to issue an + HTTP GET and re-rendering the whole + page.

+
+
+

Currently, thanks to hx-boost, the form will use an AJAX request for this + GET, but we currently don’t get that nice + search-as-you-type behavior that we want.

+
+
+
+ +
+

To add active search behavior, we will need to add a few htmx attributes to the search input. + We will leave the current + form as it is, with an action and method, so that, in case a user + does not have JavaScript enabled, the normal + search behavior continues to work. This will make our “Active Search” + improvement a nice “progressive enhancement”.

+
+
+

So, in addition to the regular form behavior, we also want to issue an HTTP + GET request when a key up occurs. We want + to issue this request to the same URL as the normal form submission. Finally, we only want + to do this after a small + pause in typing has occurred.

+
+
+

As we said, this functionality is very similar to what we needed for email validation + isn’t it? We can, in fact copy + the hx-trigger attribute directly from our email validation example, with its + small 200-millisecond delay, to allow a + user to stop typing before a request is triggered.

+
+
+

Again, a great example of how common patterns come up again and again when you are using + htmx.

+
+
+
Adding Active Search Behavior
+
+
<form action="/contacts" method="get" class="tool-bar">
     <label for="search">Search Term</label>
     <input id="search" type="search" name="q" value="{{ request.args.get('q') or '' }}" (1)
            hx-get="/contacts" (2)
            hx-trigger="search, keyup delay:200ms changed"/> (3)
     <input type="submit" value="Search"/>
 </form>
-
-
-
-
    -
  1. -

    Keep the original attributes, so search will work if JavaScript is not available

    -
  2. -
  3. -

    Issue a GET to the same URL as the form

    -
  4. -
  5. -

    Nearly the same hx-trigger specification as for the email input validation

    -
  6. -
-
-
-

We did make a small change to the hx-trigger attribute: we switched out the change event for the search event. -The search event is triggered when someone clears the search or hits the enter key. It is a non-standard event, but -it doesn’t hurt to include here. The main functionality of the feature is provided by the second triggering event, the keyup -which, as with the email example, is delayed with the delay:200ms modifier to “debounce” the input requests and -avoid hammering our server with requests on every keyup.

-
-
-
-

Targeting The Correct Element

-
-

What we have is already pretty close to what we want, but we need to set up the correct target. Recall that the default -target for an element is itself. As things currently stand, an HTTP GET request will be issued to the /contacts path, -which will, as of now, return an entire HTML document of search results, and then this whole document will be inserted -into the inner HTML of the search input.

-
-
-

This is, in fact, nonsense: input elements aren’t allowed to have any HTML inside of them. The browser will, -sensibly, just ignore the htmx request to put the response HTML inside the input. So, at this point, when a user -types anything into our input, a request will be issued (you can see it in your browser development console if you try -it out) but, unfortunately, it will appear to the user as if nothing has happened at all.

-
-
-

To fix this issue, what do we want to target with the update instead? Ideally we’d like to just target the actual -results: there is no reason to update the header or search input, and that could cause an annoying flash as focus jumps -around.

-
-
-

The hx-target attribute allows us to do exactly that. Let’s use it to target the results body, the tbody element in -the table of contacts:

-
-
-
Adding Active Search Behavior
-
-
<form action="/contacts" method="get" class="tool-bar">
+                        
+
+
+
    +
  1. +

    Keep the original attributes, so search will work if JavaScript is not available

    +
  2. +
  3. +

    Issue a GET to the same URL as the form

    +
  4. +
  5. +

    Nearly the same hx-trigger specification as for the email input + validation

    +
  6. +
+
+
+

We did make a small change to the hx-trigger attribute: we switched out the + change event for the search event. + The search event is triggered when someone clears the search or hits the enter + key. It is a non-standard event, but + it doesn’t hurt to include here. The main functionality of the feature is provided by + the second triggering event, the keyup + which, as with the email example, is delayed with the delay:200ms modifier to + “debounce” the input requests and + avoid hammering our server with requests on every keyup.

+
+
+
+

Targeting The Correct Element

+
+

What we have is already pretty close to what we want, but we need to set up the correct + target. Recall that the default + target for an element is itself. As things currently stand, an HTTP GET request + will be issued to the /contacts path, + which will, as of now, return an entire HTML document of search results, and then this whole + document will be inserted + into the inner HTML of the search input.

+
+
+

This is, in fact, nonsense: input elements aren’t allowed to have any HTML + inside of them. The browser will, + sensibly, just ignore the htmx request to put the response HTML inside the input. So, at + this point, when a user + types anything into our input, a request will be issued (you can see it in your browser + development console if you try + it out) but, unfortunately, it will appear to the user as if nothing has happened at all. +

+
+
+

To fix this issue, what do we want to target with the update instead? Ideally we’d like + to just target the actual + results: there is no reason to update the header or search input, and that could cause an + annoying flash as focus jumps + around.

+
+
+

The hx-target attribute allows us to do exactly that. Let’s use it to + target the results body, the tbody element in + the table of contacts:

+
+
+
Adding Active Search Behavior
+
+
<form action="/contacts" method="get" class="tool-bar">
     <label for="search">Search Term</label>
     <input id="search" type="search" name="q" value="{{ request.args.get('q') or '' }}"
            hx-get="/contacts"
@@ -7125,89 +11701,108 @@ the table of contacts:

... </tbody> </table>
-
-
-
-
    -
  1. -

    Target the tbody tag on the page

    -
  2. -
-
-
-

Because there is only one tbody on the page, we can use the general CSS selector tbody and htmx will target the -body of the table on the page.

-
-
-

Now if you try typing something into the search box, we’ll see some results: a request is made and the results are inserted -into the document within the tbody. Unfortunately, the content that is coming back is still an entire HTML document.

-
-
-

Here we end up with a “double render” situation, where an entire document has been inserted inside another element, with -all the navigation, headers and footers and so forth re-rendered within that element. This is an example of one of those -silly mis-targeting issues we mentioned earlier.

-
-
-

Thankfully, it is pretty easy to fix.

-
-
-
-

Paring Down Our Content

-
-

Now, we could use the same trick we reached for in the “Click To Load” and “Infinite Scroll” features: the hx-select -attribute. Recall that the hx-select attribute allows us to pick out the part of the response we are interested in using -a CSS selector.

-
-
-

So we could add this to our input:

-
-
-
Using hx-select for Active Search
-
-
<input id="search" type="search" name="q" value="{{ request.args.get('q') or '' }}"
+                        
+
+
+
    +
  1. +

    Target the tbody tag on the page

    +
  2. +
+
+
+

Because there is only one tbody on the page, we can use the general CSS selector + tbody and htmx will target the + body of the table on the page.

+
+
+

Now if you try typing something into the search box, we’ll see some results: a request + is made and the results are inserted + into the document within the tbody. Unfortunately, the content that is coming + back is still an entire HTML document.

+
+
+

Here we end up with a “double render” situation, where an entire document has + been inserted inside another element, with + all the navigation, headers and footers and so forth re-rendered within that element. This + is an example of one of those + silly mis-targeting issues we mentioned earlier.

+
+
+

Thankfully, it is pretty easy to fix.

+
+
+
+

Paring Down Our Content

+
+

Now, we could use the same trick we reached for in the “Click To Load” and + “Infinite Scroll” features: the hx-select + attribute. Recall that the hx-select attribute allows us to pick out the part + of the response we are interested in using + a CSS selector.

+
+
+

So we could add this to our input:

+
+
+
Using hx-select for Active Search
+
+
<input id="search" type="search" name="q" value="{{ request.args.get('q') or '' }}"
        hx-get="/contacts"
        hx-trigger="change, keyup delay:200ms changed"
        hx-target="tbody"
        hx-select="tbody tr"/> (1)
-
-
-
-
    -
  1. -

    Adding an hx-select that picks out the table rows in the tbody of the response

    -
  2. -
-
-
-

However, that isn’t the only fix for this problem, and, in this case, it isn’t the most efficient one. Instead, let’s -change the server-side of our Hypermedia-Driven Application to serve only the HTML content needed.

-
-
-
-

HTTP Request Headers In htmx

-
-

In this section, we’ll look at another, more advanced technique for dealing with a situation where we only want a partial -bit of HTML, rather than a full document. Currently, we are letting the server create the full HTML document as response -and then, on the client side, we filter the HTML down to the bits that we want. This is easy to do, and, in fact, might -be necessary if we don’t control the server side or can’t easily modify responses.

-
-
-

In our application, however, since we are doing “Full Stack” development (that is: we control both the front end and the back end -code, and can easily modify either) we have another option: we can modify our server responses to return only the content -necessary, and remove the need to do client-side filtering.

-
-
-

This turns out to be more efficient, since we aren’t returning all the content surrounding the bit we are interested in, -saving bandwidth as well as CPU and memory on the server side. So let’s take this opportunity to explore returning -different HTML content based on the context information that htmx provides with the HTTP requests it makes.

-
-
-

Here’s a look again at the current server-side code for our search logic:

-
-
-
Server Side Search
-
-
@app.route("/contacts")
+                        
+
+
+
    +
  1. +

    Adding an hx-select that picks out the table rows in the + tbody of the response

    +
  2. +
+
+
+

However, that isn’t the only fix for this problem, and, in this case, it isn’t + the most efficient one. Instead, let’s + change the server-side of our Hypermedia-Driven Application to serve only the + HTML content needed.

+
+
+
+

HTTP Request Headers In htmx

+
+

In this section, we’ll look at another, more advanced technique for dealing with a + situation where we only want a partial + bit of HTML, rather than a full document. Currently, we are letting the server + create the full HTML document as response + and then, on the client side, we filter the HTML down to the bits that we want. This is easy + to do, and, in fact, might + be necessary if we don’t control the server side or can’t easily modify + responses.

+
+
+

In our application, however, since we are doing “Full Stack” development (that + is: we control both the front end and the back end + code, and can easily modify either) we have another option: we can modify our server + responses to return only the content + necessary, and remove the need to do client-side filtering.

+
+
+

This turns out to be more efficient, since we aren’t returning all the content + surrounding the bit we are interested in, + saving bandwidth as well as CPU and memory on the server side. So let’s take this + opportunity to explore returning + different HTML content based on the context information that htmx provides with the HTTP + requests it makes.

+
+
+

Here’s a look again at the current server-side code for our search logic:

+
+
+
Server Side Search
+
+
@app.route("/contacts")
 def contacts():
     search = request.args.get("q")
     if search is not None:
@@ -7215,49 +11810,59 @@ def contacts():
     else:
         contacts_set = Contact.all()
     return render_template("index.html", contacts=contacts_set) (2)
-
-
-
-
    -
  1. -

    This is where the search logic happens

    -
  2. -
  3. -

    We simply re-render the index.html template every time, no matter what

    -
  4. -
-
-
-

How do we want to change this? We want to render two different bits of HTML content conditionally:

-
-
-
    -
  • -

    If this is a “normal” request for the entire page, we want to render the index.html template in the current -manner. In fact, we don’t want anything to change if this is a “normal” request.

    -
  • -
  • -

    However, if this is an “Active Search” request, we only want to render the content that is within the tbody, -that is, just the table rows of the page.

    -
  • -
-
-
-

So we need some way to determine exactly which of these two different types of requests to the /contact URL is being -made, in order to know exactly which content we want to render.

-
-
-

It turns out that htmx helps us distinguish between these two cases by including a number of HTTP Request Headers when -it makes requests. Request Headers are a feature of HTTP, allowing clients (e.g. web browsers) to include name/value pairs -of metadata associated with requests to help the server understand what the client is requesting.

-
-
-

Here is an example of (some of) the headers the FireFox browser issues when requesting https://manning.com:

-
-
-
HTTP Headers
-
-
GET / HTTP/2
+                        
+
+
+
    +
  1. +

    This is where the search logic happens

    +
  2. +
  3. +

    We simply re-render the index.html template every time, no matter what +

    +
  4. +
+
+
+

How do we want to change this? We want to render two different bits of HTML content + conditionally:

+
+
+
    +
  • +

    If this is a “normal” request for the entire page, we want to render the + index.html template in the current + manner. In fact, we don’t want anything to change if this is a + “normal” request.

    +
  • +
  • +

    However, if this is an “Active Search” request, we only want to render + the content that is within the tbody, + that is, just the table rows of the page.

    +
  • +
+
+
+

So we need some way to determine exactly which of these two different types of requests to + the /contact URL is being + made, in order to know exactly which content we want to render.

+
+
+

It turns out that htmx helps us distinguish between these two cases by including a number of + HTTP Request Headers when + it makes requests. Request Headers are a feature of HTTP, allowing clients (e.g. web + browsers) to include name/value pairs + of metadata associated with requests to help the server understand what the client is + requesting.

+
+
+

Here is an example of (some of) the headers the FireFox browser issues when requesting + https://manning.com:

+
+
+
HTTP Headers
+
+
GET / HTTP/2
 Host: www.manning.com
 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:103.0) Gecko/20100101 Firefox/103.0
 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
@@ -7273,65 +11878,73 @@ Sec-Fetch-Site: none
 Sec-Fetch-User: ?1
 Sec-GPC: 1
 TE: trailers
-
-
-
-

htmx takes advantage of this feature of HTTP and adds additional headers and, therefore, additional context to the -HTTP requests that it makes. This allows you to inspect those headers and make smarter decisions with respect to exactly -what logic to execute on the server, and what sort of HTML response you want to send to the client.

-
-
-

Here is a table of the HTTP headers that htmx includes in HTTP requests:

-
-
-
-
HX-Boosted
-
-

This will be the string “true” if the request is made via an element using hx-boost

-
-
HX-Current-URL
-
-

This will be the current URL of the browser

-
-
HX-History-Restore-Request
-
-

This will be the string “true” if the request is for history restoration after a miss in the local history cache

-
-
HX-Prompt
-
-

This will contain the user response to an hx-prompt

-
-
HX-Request
-
-

This value is always “true” for htmx-based requests

-
-
HX-Target
-
-

This value will be the id of the target element if it exists

-
-
HX-Trigger-Name
-
-

This value will be the name of the triggered element if it exists

-
-
HX-Trigger
-
-

This value will be the id of the triggered element if it exists

-
-
-
-
-

Looking through this list of headers, the last one stands out: we have an id, search on our search input. So the -value of the HX-Trigger header should be set to search when the request is coming from the search input, which -has the id search.

-
-
-

Let’s add some conditional logic to our controller to look for that header and, if the value is search, we render -only the rows rather than the whole index.html template:

-
-
-
Updating Our Server Side Search
-
-
@app.route("/contacts")
+                        
+
+
+

htmx takes advantage of this feature of HTTP and adds additional headers and, therefore, + additional context to the + HTTP requests that it makes. This allows you to inspect those headers and make smarter + decisions with respect to exactly + what logic to execute on the server, and what sort of HTML response you want to send to the + client.

+
+
+

Here is a table of the HTTP headers that htmx includes in HTTP requests:

+
+
+
+
HX-Boosted
+
+

This will be the string “true” if the request is made via an element + using hx-boost

+
+
HX-Current-URL
+
+

This will be the current URL of the browser

+
+
HX-History-Restore-Request
+
+

This will be the string “true” if the request is for history restoration + after a miss in the local history cache

+
+
HX-Prompt
+
+

This will contain the user response to an hx-prompt

+
+
HX-Request
+
+

This value is always “true” for htmx-based requests

+
+
HX-Target
+
+

This value will be the id of the target element if it exists

+
+
HX-Trigger-Name
+
+

This value will be the name of the triggered element if it exists

+
+
HX-Trigger
+
+

This value will be the id of the triggered element if it exists

+
+
+
+
+

Looking through this list of headers, the last one stands out: we have an id, + search on our search input. So the + value of the HX-Trigger header should be set to search when the + request is coming from the search input, which + has the id search.

+
+
+

Let’s add some conditional logic to our controller to look for that header and, if the + value is search, we render + only the rows rather than the whole index.html template:

+
+
+
Updating Our Server Side Search
+
+
@app.route("/contacts")
 def contacts():
     search = request.args.get("q")
     if search is not None:
@@ -7341,38 +11954,43 @@ def contacts():
     else:
         contacts_set = Contact.all()
     return render_template("index.html", contacts=contacts_set) (2)
-
-
-
-
    -
  1. -

    If the request header HX-Trigger is equal to “search”, we want to do something different

    -
  2. -
  3. -

    We need to learn how to render just the table rows

    -
  4. -
-
-
-

OK, so how do we render only the result rows?

-
-
-
-

Factoring Your Templates

-
-

Now we come to what is a common pattern in htmx: we want to factor our server-side templates. This means that we want to -break our templates up a bit so that they can be called from multiple contexts. In this case, we want to break the rows of -the results table out to a separate template. We will call this new template rows.html and we will include it from -the original index.html template, and also use it in our controller to render it by itself when we want to respond with only the -rows for Active Search requests.

-
-
-

Here’s what the table in our index.html file currently looks like:

-
-
-
The Contacts Table
-
-
    <table>
+                        
+
+
+
    +
  1. +

    If the request header HX-Trigger is equal to “search”, we + want to do something different

    +
  2. +
  3. +

    We need to learn how to render just the table rows

    +
  4. +
+
+
+

OK, so how do we render only the result rows?

+
+
+
+

Factoring Your Templates

+
+

Now we come to what is a common pattern in htmx: we want to factor our server-side + templates. This means that we want to + break our templates up a bit so that they can be called from multiple contexts. In this + case, we want to break the rows of + the results table out to a separate template. We will call this new template + rows.html and we will include it from + the original index.html template, and also use it in our controller to render + it by itself when we want to respond with only the + rows for Active Search requests.

+
+
+

Here’s what the table in our index.html file currently looks like:

+
+
+
The Contacts Table
+
+
    <table>
         <thead>
         <tr>
             <th>First</th> <th>Last</th> <th>Phone</th> <th>Email</th> <th></th>
@@ -7391,20 +12009,22 @@ rows for Active Search requests.

{% endfor %} </tbody> </table>
-
-
-
-

The for loop in this template is what produces all the rows in the final content generated by index.html. -What we want to do is to move the for loop and, therefore, the rows it creates out to a separate template file so that -only that small bit of HTML can be rendered independently from index.html.

-
-
-

Again, let’s call this new template rows.html:

-
-
-
Our New rows.html file
-
-
{% for contact in contacts %} (2)
+                        
+
+
+

The for loop in this template is what produces all the rows in the final content + generated by index.html. + What we want to do is to move the for loop and, therefore, the rows it creates + out to a separate template file so that + only that small bit of HTML can be rendered independently from index.html.

+
+
+

Again, let’s call this new template rows.html:

+
+
+
Our New rows.html file
+
+
{% for contact in contacts %} (2)
     <tr>
         <td>{{ contact.first }}</td>
         <td>{{ contact.last }}</td>
@@ -7414,21 +12034,25 @@ only that small bit of HTML can be rendered independently from index.html<
             <a href="/contacts/{{ contact.id }}">View</a></td>
     </tr>
 {% endfor %}
-
-
-
-

Using this template we can render only the tr elements for a given collection of contacts.

-
-
-

Of course, we still want to include this content in the index.html template: we are sometimes going to be -rendering the entire page, and sometimes only rendering the rows. In order to keep the index.html template rendering -properly, we can include the rows.html template by using the jinja include directive at the position we want the content -from rows.html inserted:

-
-
-
Including The New File
-
-
    <table>
+                        
+
+
+

Using this template we can render only the tr elements for a given collection of + contacts.

+
+
+

Of course, we still want to include this content in the index.html template: we + are sometimes going to be + rendering the entire page, and sometimes only rendering the rows. In order to keep the + index.html template rendering + properly, we can include the rows.html template by using the jinja + include directive at the position we want the content + from rows.html inserted:

+
+
+
Including The New File
+
+
    <table>
         <thead>
         <tr>
             <th>First</th>
@@ -7442,35 +12066,41 @@ from rows.html inserted:

{% include 'rows.html' %} (1) </tbody> </table>
-
-
-
-
    -
  1. -

    This directive “includes” the rows.html file, inserting its content into the current template

    -
  2. -
-
-
-

So far, so good: our /contacts page is still rendering properly, just as it did before we split the rows out of the -index.html template.

-
-
-
-

Using Our New Template

-
-

The last step in factoring our templates is to modify our web controller to take advantage of the new rows.html template -file when it responds to an active search request.

-
-
-

Since rows.html is just another template, just like index.html, all we need to do is call the render_template -function with rows.html rather than index.html, and we will render only the row content rather than the entire -page:

-
-
-
Updating Our Server Side Search
-
-
@app.route("/contacts")
+                        
+
+
+
    +
  1. +

    This directive “includes” the rows.html file, inserting its + content into the current template

    +
  2. +
+
+
+

So far, so good: our /contacts page is still rendering properly, just as it did + before we split the rows out of the + index.html template. +

+
+
+
+

Using Our New Template

+
+

The last step in factoring our templates is to modify our web controller to take advantage of + the new rows.html template + file when it responds to an active search request.

+
+
+

Since rows.html is just another template, just like index.html, all + we need to do is call the render_template + function with rows.html rather than index.html, and we will render + only the row content rather than the entire + page:

+
+
+
Updating Our Server Side Search
+
+
@app.route("/contacts")
 def contacts():
     search = request.args.get("q")
     if search is not None:
@@ -7480,206 +12110,276 @@ def contacts():
     else:
         contacts_set = Contact.all()
     return render_template("index.html", contacts=contacts_set)
-
-
-
-
    -
  1. -

    Render the new template in the case of an active search

    -
  2. -
-
-
-

Now, when an Active Search request is made, rather than getting an entire HTML document back, we only get a partial -bit of HTML, the table rows for the contacts that match the search. These rows are then inserted into the tbody on -the index page, without any need for an hx-select or any other client-side processing.

-
-
-

And, as a bonus, the old form-based search still works as well, thanks to the fact that we conditionally render the rows -only when the search input issues the HTTP request via htmx. Again, this is a progressive enhancement to our -application.

-
- -
-
-

Updating The Navigation Bar With hx-push-url

-
-

One shortcoming of our current Active Search implementation, when compared with the normal form submission, is that when -you submit the form version it updates the navigation bar of the browser to include the search term. So, for example, if -you search for “joe” in the search box, you will end up with a url that looks like this in your browser’s nav bar:

-
-
-
The Updated Location After A Form Search
-
-
https://example.com/contacts?q=joe
-
-
-
-

This is a nice feature of browsers: it allows you to bookmark this search or to copy the URL and send it to someone else. -All they have to do is to click on the link, and they will repeat the exact same search. This is also tied in with -the browser’s notion of history: if you click the back button it will take you to the previous URL that you came -from. If you submit two searches and want to go back to the first one, you can simply hit back and the browser -will “return” to that search.

-
-
-

As it stands right now, during our Active Search, we are not updating the browser’s navigation bar, so users aren’t getting -nice copy-and-pasteable links and you aren’t getting history entries either, so no back button support. Fortunately, htmx -provides a way for fixing this that we’ve already seen: the hx-push-url attribute.

-
-
-

The hx-push-url attribute lets you tell htmx “Please push the URL of this request into the browser’s navigation bar”. -Push might seem like an odd verb to use here, but that’s the term that the underlying browser history API uses, which -stems from the fact that it models browser history as a “stack” of locations: when you go to a new location, that -location is “pushed” onto the stack of history elements, and when you click “back”, that location is “popped” off -the history stack.

-
-
-

So, to get proper history support for our Active Search, all we need to do is to set the hx-push-url attribute to -true. Let’s update our search input:

-
-
-
Updating The URL During Active Search
-
-
<input id="search" type="search" name="q" value="{{ request.args.get('q') or '' }}"
+                        
+
+
+
    +
  1. +

    Render the new template in the case of an active search

    +
  2. +
+
+
+

Now, when an Active Search request is made, rather than getting an entire HTML document back, + we only get a partial + bit of HTML, the table rows for the contacts that match the search. These rows are then + inserted into the tbody on + the index page, without any need for an hx-select or any other client-side + processing.

+
+
+

And, as a bonus, the old form-based search still works as well, thanks to the fact that we + conditionally render the rows + only when the search input issues the HTTP request via htmx. Again, this is a + progressive enhancement to our + application.

+
+ +
+
+

Updating The Navigation Bar With + hx-push-url

+
+

One shortcoming of our current Active Search implementation, when compared with the normal + form submission, is that when + you submit the form version it updates the navigation bar of the browser to include the + search term. So, for example, if + you search for “joe” in the search box, you will end up with a url that looks + like this in your browser’s nav bar:

+
+
+
The Updated Location After A Form Search
+
+
https://example.com/contacts?q=joe
+
+
+
+

This is a nice feature of browsers: it allows you to bookmark this search or to copy the URL + and send it to someone else. + All they have to do is to click on the link, and they will repeat the exact same search. + This is also tied in with + the browser’s notion of history: if you click the back button it will take you to the + previous URL that you came + from. If you submit two searches and want to go back to the first one, you can simply hit + back and the browser + will “return” to that search.

+
+
+

As it stands right now, during our Active Search, we are not updating the browser’s + navigation bar, so users aren’t getting + nice copy-and-pasteable links and you aren’t getting history entries either, so no + back button support. Fortunately, htmx + provides a way for fixing this that we’ve already seen: the hx-push-url + attribute.

+
+
+

The hx-push-url attribute lets you tell htmx “Please push the URL of this + request into the browser’s navigation bar”. + Push might seem like an odd verb to use here, but that’s the term that the underlying + browser history API uses, which + stems from the fact that it models browser history as a “stack” of locations: + when you go to a new location, that + location is “pushed” onto the stack of history elements, and when you click + “back”, that location is “popped” off + the history stack.

+
+
+

So, to get proper history support for our Active Search, all we need to do is to set the + hx-push-url attribute to + true. Let’s update our search input: +

+
+
+
Updating The URL During Active Search
+
+
<input id="search" type="search" name="q" value="{{ request.args.get('q') or '' }}"
        hx-get="/contacts"
        hx-trigger="change, keyup delay:200ms changed"
        hx-target="tbody"
        hx-push-url="true"/> (1)
-
-
-
-
    -
  1. -

    By adding the hx-push-url attribute with the value true, htmx will update the URL when it makes a request

    -
  2. -
-
-
-

Now, as Active Search requests are sent, the URL in the browser’s navigation bar is updated to have the proper query in -it, just like when the form is submitted.

-
-
-

Now, you might not want this behavior. You might feel it would be confusing to users to see the navigation bar updated -and have history entries for every Active Search made, for example. Which is fine: you can simply omit the hx-push-url -attribute and it will go back to the behavior you want. Htmx tries to be flexible enough that you can achieve the UX -that you want, while staying within the declarative HTML model.

-
-
-
-

Adding A Request Indicator

-
-

A final touch for our Active Search pattern is to add a request indicator to let the user know that a search is in -progress. As it stands the user has to know that the active search functionality is doing a request implicitly and, -if the search takes a bit, may end up thinking that the feature isn’t working. By adding a request indicator we let -the user know that the hypermedia application is busy and they should wait (hopefully not too long!) for the request to -complete.

-
-
-

htmx provides support for request indicators via the hx-indicator attribute. This attribute takes, you guessed it, -a CSS selector that points to the indicator for a given element. The indicator can be anything, but it is typically -some sort of animated image, such as a gif or svg file, that spins or otherwise communicates visually that “something -is happening”.

-
-
-

Let’s add a spinner after our search input:

-
-
-
Updating The URL During Active Search
-
-
<input id="search" type="search" name="q" value="{{ request.args.get('q') or '' }}"
+                        
+
+
+
    +
  1. +

    By adding the hx-push-url attribute with the value true, + htmx will update the URL when it makes a request

    +
  2. +
+
+
+

Now, as Active Search requests are sent, the URL in the browser’s navigation bar is + updated to have the proper query in + it, just like when the form is submitted.

+
+
+

Now, you might not want this behavior. You might feel it would be confusing to users + to see the navigation bar updated + and have history entries for every Active Search made, for example. Which is fine: you can + simply omit the hx-push-url + attribute and it will go back to the behavior you want. Htmx tries to be flexible enough + that you can achieve the UX + that you want, while staying within the declarative HTML model.

+
+
+
+

Adding A Request Indicator

+
+

A final touch for our Active Search pattern is to add a request indicator to let the user + know that a search is in + progress. As it stands the user has to know that the active search functionality is doing a + request implicitly and, + if the search takes a bit, may end up thinking that the feature isn’t working. By + adding a request indicator we let + the user know that the hypermedia application is busy and they should wait (hopefully not + too long!) for the request to + complete.

+
+
+

htmx provides support for request indicators via the hx-indicator attribute. + This attribute takes, you guessed it, + a CSS selector that points to the indicator for a given element. The indicator can be + anything, but it is typically + some sort of animated image, such as a gif or svg file, that spins or otherwise communicates + visually that “something + is happening”.

+
+
+

Let’s add a spinner after our search input:

+
+
+
Updating The URL During Active Search
+
+
<input id="search" type="search" name="q" value="{{ request.args.get('q') or '' }}"
        hx-get="/contacts"
        hx-trigger="change, keyup delay:200ms changed"
        hx-target="tbody"
        hx-push-url="true"
        hx-indicator="#spinner"/> (1)
 <img id="spinner" class="htmx-indicator" src="/static/img/spinning-circles.svg" alt="Request In Flight..."/> (2)
-
-
-
-
    -
  1. -

    The hx-indicator attribute points to the indicator image after the input

    -
  2. -
  3. -

    The indicator is a spinning circle svg file, and has the htmx-indicator class on it

    -
  4. -
-
-
-

We have added the spinner right after the input. This visually co-locates the request indicator with the element -making the request, and makes it easy for a user to see that something is in fact happening.

-
-
-

Note that the indicator img tag has the htmx-indicator class on it. htmx-indicator is a CSS class that is -automatically injected into the page by htmx. This class sets the default opacity of an element to 0, which hides -the element from view, while at the same time not disrupting the layout of the page.

-
-
-

When an htmx request is triggered that points to this indicator, another class, htmx-request is added to the indicator -which transitions its opacity to 1. So you can use just about anything as an indicator, and it will be hidden by default, -and then, when a request is in flight, will be shown. This is all done via standard CSS classes, allowing you to control -the transitions and even the mechanism by which the indicator is shown (e.g. you might use display rather than -opacity). htmx is flexible in this regard.

-
- -
-

With this request indicator, we now have a pretty sophisticated user experience built out when compared with plain HTML, but -we’ve built it all as a hypermedia-driven feature. No JSON or JavaScript to be seen. And this particular implementation also -has the benefit of being a progressive enhancement, so this aspect of our application will continue to work for clients -that don’t have JavaScript enabled.

-
-
-
-
-

7.2. Lazy Loading

-
-

With Active Search behind us, let’s move on to a very different sort of problem: lazy loading. Lazy loading is -when the loading of a particular bit of content is deferred until later, when needed. This is commonly used as a -performance enhancement: you avoid the processing resources necessary to produce some data until that data is actually -needed.

-
-
-

Let’s add a count of the total number of contacts to Contact.app, just below the bottom of our contacts table. This will -give us a potentially expensive operation that we can use to demonstrate how easy it is to add lazy loading to our -application with htmx.

-
-
-

First let’s update our server code in the /contacts request handler to get a count of the total number of contacts. -We will pass that count through to the template to render some new HTML.

-
-
-
Adding A Count To The UI
-
-
@app.route("/contacts")
+                        
+
+
+
    +
  1. +

    The hx-indicator attribute points to the indicator image after the input +

    +
  2. +
  3. +

    The indicator is a spinning circle svg file, and has the htmx-indicator + class on it

    +
  4. +
+
+
+

We have added the spinner right after the input. This visually co-locates the request + indicator with the element + making the request, and makes it easy for a user to see that something is in fact happening. +

+
+
+

Note that the indicator img tag has the htmx-indicator class on it. + htmx-indicator is a CSS class that is + automatically injected into the page by htmx. This class sets the default + opacity of an element to 0, which hides + the element from view, while at the same time not disrupting the layout of the page.

+
+
+

When an htmx request is triggered that points to this indicator, another class, + htmx-request is added to the indicator + which transitions its opacity to 1. So you can use just about anything as an indicator, and + it will be hidden by default, + and then, when a request is in flight, will be shown. This is all done via standard CSS + classes, allowing you to control + the transitions and even the mechanism by which the indicator is shown (e.g. you might use + display rather than + opacity). htmx is flexible in this regard. +

+
+ +
+

With this request indicator, we now have a pretty sophisticated user experience built out + when compared with plain HTML, but + we’ve built it all as a hypermedia-driven feature. No JSON or JavaScript to be seen. + And this particular implementation also + has the benefit of being a progressive enhancement, so this aspect of our application will + continue to work for clients + that don’t have JavaScript enabled.

+
+
+
+
+

7.2. Lazy Loading

+
+

With Active Search behind us, let’s move on to a very different sort of problem: lazy + loading. Lazy loading is + when the loading of a particular bit of content is deferred until later, when needed. This is + commonly used as a + performance enhancement: you avoid the processing resources necessary to produce some data until + that data is actually + needed.

+
+
+

Let’s add a count of the total number of contacts to Contact.app, just below the bottom of + our contacts table. This will + give us a potentially expensive operation that we can use to demonstrate how easy it is to add + lazy loading to our + application with htmx.

+
+
+

First let’s update our server code in the /contacts request handler to get a + count of the total number of contacts. + We will pass that count through to the template to render some new HTML.

+
+
+
Adding A Count To The UI
+
+
@app.route("/contacts")
 def contacts():
     search = request.args.get("q")
     page = int(request.args.get("page", 1))
@@ -7691,114 +12391,129 @@ def contacts():
     else:
         contacts_set = Contact.all(page)
     return render_template("index.html", contacts=contacts_set, page=page, count=count) (2)
-
-
-
-
    -
  1. -

    Get the total count of contacts from the Contact model

    -
  2. -
  3. -

    Pass the count out to the index.html template to use when rendering

    -
  4. -
-
-
-

As with the rest of the application, in the interest of staying focused on the hypermedia part of Contact.app, we are -not going to look into the details of how Contact.count() works. We just need to know that:

-
-
-
    -
  • -

    It returns the total count of contacts in the contact database

    -
  • -
  • -

    It may potentially be slow

    -
  • -
-
-
-

Next lets add some HTML to our index.html that takes advantage of this new bit of data, showing a message next -to the "Add Contact" link with the total count of users. Here is what our HTML looks like:

-
-
-
Adding A Contact Count Element To The Application
-
-
<p>
+                    
+
+
+
    +
  1. +

    Get the total count of contacts from the Contact model

    +
  2. +
  3. +

    Pass the count out to the index.html template to use when rendering

    +
  4. +
+
+
+

As with the rest of the application, in the interest of staying focused on the + hypermedia part of Contact.app, we are + not going to look into the details of how Contact.count() works. We just need to + know that:

+
+
+
    +
  • +

    It returns the total count of contacts in the contact database

    +
  • +
  • +

    It may potentially be slow

    +
  • +
+
+
+

Next lets add some HTML to our index.html that takes advantage of this new bit of + data, showing a message next + to the "Add Contact" link with the total count of users. Here is what our HTML looks like:

+
+
+
Adding A Contact Count Element To The Application
+
+
<p>
     <a href="/contacts/new">Add Contact</a> <span>({{ count }} total Contacts)</span>(1)
 </p>
-
-
-
-
    -
  1. -

    A simple span with some text showing the total number of contacts.

    -
  2. -
-
-
-

Well that was easy, wasn’t it? Now our users will see the total number of contacts next to the link to add new -contacts, to give them a sense of how large the contact database is. This sort of rapid development is one of the -joys of developing web applications the old way.

-
-
-

Here is what the feature looks like in our application:

-
-
-
-screenshot total contacts -
-
Figure 5. Total Contact Count Display
-
-
-

Beautiful.

-
-
-

Of course, as you probably suspected, all is not perfect. Unfortunately, upon shipping this feature to production, we -start getting some complaints from the users that the application “feels slow”. Like all good developers faced with -a performance issue, rather than guessing what the issue might be, we try to get a performance profile of the application -to see what exactly is causing the problem.

-
-
-

It turns out, surprisingly, that the problem is that innocent looking Contacts.count() call, which is taking up to -a second and a half to complete. Unfortunately, for reasons beyond the scope of this book, it is not possible to improve -that load time, nor is possible to cache the result.

-
-
-

This leaves us with two choices:

-
-
-
    -
  • -

    Remove the feature

    -
  • -
  • -

    Come up with some other way to mitigate the performance issue

    -
  • -
-
-
-

Let’s assume that we can’t remove the feature, and therefore look at how we can mitigate this performance issue by -using htmx instead.

-
-
-

Pulling The Expensive Code Out

-
-

The first step in implementing the Lazy Load pattern is to pull the expensive code, that is, the call to Contacts.count() -out of request handler for the /contacts end point.

-
-
-

Let’s pull this function call into its own HTTP request handler as a new HTTP end point that we will put at /contacts/count. -For this new end point, we won’t need to render a template at all: its sole job is going to be to render that small bit of text -that is in the span, “(22 total Contacts)”

-
-
-

Here is what the new code will look like:

-
-
-
Pulling The Expensive Code Out
-
-
@app.route("/contacts")
+                    
+
+
+
    +
  1. +

    A simple span with some text showing the total number of contacts.

    +
  2. +
+
+
+

Well that was easy, wasn’t it? Now our users will see the total number of contacts next to + the link to add new + contacts, to give them a sense of how large the contact database is. This sort of rapid + development is one of the + joys of developing web applications the old way.

+
+
+

Here is what the feature looks like in our application:

+
+
+
+ screenshot total contacts +
+
Figure 5. Total Contact Count Display
+
+
+

Beautiful.

+
+
+

Of course, as you probably suspected, all is not perfect. Unfortunately, upon shipping this + feature to production, we + start getting some complaints from the users that the application “feels slow”. Like + all good developers faced with + a performance issue, rather than guessing what the issue might be, we try to get a performance + profile of the application + to see what exactly is causing the problem.

+
+
+

It turns out, surprisingly, that the problem is that innocent looking + Contacts.count() call, which is taking up to + a second and a half to complete. Unfortunately, for reasons beyond the scope of this book, it is + not possible to improve + that load time, nor is possible to cache the result.

+
+
+

This leaves us with two choices:

+
+
+
    +
  • +

    Remove the feature

    +
  • +
  • +

    Come up with some other way to mitigate the performance issue

    +
  • +
+
+
+

Let’s assume that we can’t remove the feature, and therefore look at how we can + mitigate this performance issue by + using htmx instead.

+
+
+

Pulling The Expensive Code Out

+
+

The first step in implementing the Lazy Load pattern is to pull the expensive code, that is, + the call to Contacts.count() + out of request handler for the /contacts end point.

+
+
+

Let’s pull this function call into its own HTTP request handler as a new HTTP end point + that we will put at /contacts/count. + For this new end point, we won’t need to render a template at all: its sole job is + going to be to render that small bit of text + that is in the span, “(22 total Contacts)”

+
+
+

Here is what the new code will look like:

+
+
+
Pulling The Expensive Code Out
+
+
@app.route("/contacts")
 def contacts():
     search = request.args.get("q")
     page = int(request.args.get("page", 1)) (1)
@@ -7814,312 +12529,387 @@ def contacts():
 def contacts_count():
     count = Contact.count() (3)
     return "(" + str(count) + " total Contacts)" (4)
-
-
-
-
    -
  1. -

    We no longer call Contacts.count() in this handler

    -
  2. -
  3. -

    count is no longer passed out to the template to render in the /contacts handler

    -
  4. -
  5. -

    We create a new handler at the /contacts/count path that does the expensive calculation

    -
  6. -
  7. -

    Return the string with the total number of contacts in it

    -
  8. -
-
-
-

So now we have moved the performance issue out of the /contacts handler code, which renders the main contacts table, -and created a new HTTP end point that will produce this expensive-to-create count string for us.

-
-
-

Now we need to get the content from this new handler into the span, somehow. As we said earlier, the default behavior -of htmx is to place any content it receives for a given request into the innerHTML of an element, and that turns out -to be exactly what we want here: we want to retrieve this text and put it into the span. So we can simply place an -hx-get attribute on the span, pointing to this new path, and do exactly that.

-
-
-

However, recall that the default event that will trigger a request for a span element in htmx is the click event. -Well, that’s not what we want! Instead, we want this request to trigger immediately, when the page loads.

-
-
-

To do this, we can add the hx-trigger attribute to update the trigger of the requests for the element, and use the -load event.

-
-
-

The load event is a special event that htmx triggers on all content when it is loaded into the DOM. By setting hx-trigger -to load, we will cause htmx to issue the GET request when the span element is loaded into the page.

-
-
-

Here is our updated template code:

-
-
-
Adding A Contact Count Element To The Application
-
-
<p>
+                        
+
+
+
    +
  1. +

    We no longer call Contacts.count() in this handler

    +
  2. +
  3. +

    count is no longer passed out to the template to render in the + /contacts handler

    +
  4. +
  5. +

    We create a new handler at the /contacts/count path that does the + expensive calculation

    +
  6. +
  7. +

    Return the string with the total number of contacts in it

    +
  8. +
+
+
+

So now we have moved the performance issue out of the /contacts handler code, + which renders the main contacts table, + and created a new HTTP end point that will produce this expensive-to-create count string for + us.

+
+
+

Now we need to get the content from this new handler into the span, somehow. As we + said earlier, the default behavior + of htmx is to place any content it receives for a given request into the + innerHTML of an element, and that turns out + to be exactly what we want here: we want to retrieve this text and put it into the + span. So we can simply place an + hx-get attribute on the span, pointing to this new path, and do exactly that. +

+
+
+

However, recall that the default event that will trigger a request for a + span element in htmx is the click event. + Well, that’s not what we want! Instead, we want this request to trigger immediately, + when the page loads.

+
+
+

To do this, we can add the hx-trigger attribute to update the trigger of the + requests for the element, and use the + load event. +

+
+
+

The load event is a special event that htmx triggers on all content when it is + loaded into the DOM. By setting hx-trigger + to load, we will cause htmx to issue the GET request when the + span element is loaded into the page.

+
+
+

Here is our updated template code:

+
+
+
Adding A Contact Count Element To The Application
+
+
<p>
     <a href="/contacts/new">Add Contact</a> <span hx-get="/contacts/count" hx-trigger="load"></span>(1)
 </p>
-
-
-
-
    -
  1. -

    Issue a GET to /contacts/count when the load event occurs

    -
  2. -
-
-
-

Note that the span starts empty: we have removed the content from it, and we are allowing the request to /contacts/count -to populate it instead.

-
-
-

And, check it out, our /contacts page is fast again! When you navigate to the page it feels very snappy and -profiling shows that yes, indeed, the page is loading much more quickly. Why is that? Well, we’ve deferred the -expensive calculation to a secondary request, allowing the initial request to finish loading much more quickly.

-
-
-

You might say “OK, great, but it’s still taking a second or two to get the total count on the page.” True, but -often the user may not be particularly interested in the total count. They may just want to come to the page and -search for an existing user, or perhaps they may want to edit or add a user. The total count of contacts -is just a “nice to have” bit of information in these cases.

-
-
-

By deferring the calculation of the count in this manner we let users get on with their use of the application while we -perform the expensive calculation.

-
-
-

Yes, the total time to get all the information on the screen takes just as long. It actually will be a bit longer, since -we now need two HTTP requests to get all the information for the page. But the perceived performance for the end user will -be much better: they can do what they want nearly immediately, even if some information isn’t available instantaneously.

-
-
-

Lazy Loading is a great tool to have in your tool belt when optimizing your web application performance.

-
-
-
-

Adding An Indicator

-
-

A shortcoming of the current implementation is that currently there is no indication that the count request is in flight, -it just appears at some point when the request finishes.

-
-
-

This isn’t ideal. What we want here is an indicator, just like we added in our Active Search example. And, in fact, we can -simply reuse that same exact spinner image, copy-and-pasted into the new HTML we have created.

-
-
-

Now, in this case, we have a one-time request and, once the request is over, we are not going to need the spinner anymore. -So it doesn’t make sense to use the exact same approach we did with the active search example. Recall that in that -case we placed a spinner after the span and using the hx-indicator attribute to point to it.

-
-
-

In this case, since the spinner is only used once, we can put it inside the content of the span. When the request -completes the content in the response will be placed inside the span, replacing the spinner with the computed contact -count. It turns out that htmx allows you to place indicators with the htmx-indicator class on them inside of elements -that issue htmx-powered requests. In the absence of an hx-indicator attribute, these internal indicators will be shown -when a request is in flight.

-
-
-

So let’s add that spinner from the active search example as the initial content in our span:

-
-
-
Adding An Indicator To Our Lazily Loaded Content
-
-
<span hx-get="/contacts/count" hx-trigger="load">
+                        
+
+
+
    +
  1. +

    Issue a GET to /contacts/count when the load + event occurs

    +
  2. +
+
+
+

Note that the span starts empty: we have removed the content from it, and we are + allowing the request to /contacts/count + to populate it instead.

+
+
+

And, check it out, our /contacts page is fast again! When you navigate to the + page it feels very snappy and + profiling shows that yes, indeed, the page is loading much more quickly. Why is that? Well, + we’ve deferred the + expensive calculation to a secondary request, allowing the initial request to finish loading + much more quickly.

+
+
+

You might say “OK, great, but it’s still taking a second or two to get the total + count on the page.” True, but + often the user may not be particularly interested in the total count. They may just want to + come to the page and + search for an existing user, or perhaps they may want to edit or add a user. The total count + of contacts + is just a “nice to have” bit of information in these cases.

+
+
+

By deferring the calculation of the count in this manner we let users get on with their use + of the application while we + perform the expensive calculation.

+
+
+

Yes, the total time to get all the information on the screen takes just as long. It actually + will be a bit longer, since + we now need two HTTP requests to get all the information for the page. But the perceived + performance for the end user will + be much better: they can do what they want nearly immediately, even if some information + isn’t available instantaneously.

+
+
+

Lazy Loading is a great tool to have in your tool belt when optimizing your web application + performance.

+
+
+
+

Adding An Indicator

+
+

A shortcoming of the current implementation is that currently there is no indication that the + count request is in flight, + it just appears at some point when the request finishes.

+
+
+

This isn’t ideal. What we want here is an indicator, just like we added in our Active + Search example. And, in fact, we can + simply reuse that same exact spinner image, copy-and-pasted into the new HTML we have + created.

+
+
+

Now, in this case, we have a one-time request and, once the request is over, we are not going + to need the spinner anymore. + So it doesn’t make sense to use the exact same approach we did with the active search + example. Recall that in that + case we placed a spinner after the span and using the hx-indicator + attribute to point to it.

+
+
+

In this case, since the spinner is only used once, we can put it inside the content + of the span. When the request + completes the content in the response will be placed inside the span, replacing the spinner + with the computed contact + count. It turns out that htmx allows you to place indicators with the + htmx-indicator class on them inside of elements + that issue htmx-powered requests. In the absence of an hx-indicator attribute, + these internal indicators will be shown + when a request is in flight.

+
+
+

So let’s add that spinner from the active search example as the initial content in our + span:

+
+
+
Adding An Indicator To Our Lazily Loaded Content
+
+
<span hx-get="/contacts/count" hx-trigger="load">
   <img id="spinner" class="htmx-indicator" src="/static/img/spinning-circles.svg"/>(1)
 </span>
-
-
-
-
    -
  1. -

    Yep, that’s it

    -
  2. -
-
-
-

Now when the user loads the page, rather than having the total contact count sprung on them like a surprise, -there is a nice spinner indicating that something is coming. Much better.

-
-
-

Note that all we had to do was copy and paste our indicator from the active search example into the span. Once again -we see a great demonstration of how htmx provides flexible, composable features and building blocks for you to -work with: implementing a new feature is often just copy-and-paste, maybe a tweak or two, and you are done.

-
-
-
-

But That’s Not Lazy!

-
-

You might say “OK, but that’s not really lazy. We are still loading the count immediately when the page is loaded, -we are just doing it in a second request. You aren’t really waiting until the value is actually needed.”

-
-
-

Fine. Let’s make it lazy lazy: we’ll only issue the request when the span scrolls into view.

-
-
-

To do that, lets recall how we set up the infinite scroll example: we used the revealed event for our trigger. That’s -all we want here, right? When the element is revealed we issue the request?

-
-
-

Yep, that’s it. Once again, we can mix and match concepts across various UX patterns to come up with solutions to -new problems in htmx.

-
-
-
Making It Lazy Lazy
-
-
<span hx-get="/contacts/count" hx-trigger="revealed"> (1)
+                        
+
+
+
    +
  1. +

    Yep, that’s it

    +
  2. +
+
+
+

Now when the user loads the page, rather than having the total contact count sprung on them + like a surprise, + there is a nice spinner indicating that something is coming. Much better.

+
+
+

Note that all we had to do was copy and paste our indicator from the active search example + into the span. Once again + we see a great demonstration of how htmx provides flexible, composable features and building + blocks for you to + work with: implementing a new feature is often just copy-and-paste, maybe a tweak or two, + and you are done.

+
+
+
+

But That’s Not Lazy!

+
+

You might say “OK, but that’s not really lazy. We are still loading the count + immediately when the page is loaded, + we are just doing it in a second request. You aren’t really waiting until the value is + actually needed.”

+
+
+

Fine. Let’s make it lazy lazy: we’ll only issue the request when the + span scrolls into view.

+
+
+

To do that, lets recall how we set up the infinite scroll example: we used the + revealed event for our trigger. That’s + all we want here, right? When the element is revealed we issue the request?

+
+
+

Yep, that’s it. Once again, we can mix and match concepts across various UX patterns to + come up with solutions to + new problems in htmx.

+
+
+
Making It Lazy Lazy
+
+
<span hx-get="/contacts/count" hx-trigger="revealed"> (1)
   <img id="spinner" class="htmx-indicator" src="/static/img/spinning-circles.svg"/>
 </span>
-
-
-
-
    -
  1. -

    Change the hx-trigger to revealed

    -
  2. -
-
-
-

Now we have a truly lazy implementation, deferring the expensive computation until we are absolutely sure we need it. A -pretty cool trick, and, again, a simple one-attribute change demonstrates the flexibility of both htmx and the hypermedia -approach.

-
-
-
-
-

7.3. Inline Delete

-
-

For our next hypermedia trick, we are going to implement the “Inline Delete” pattern. With this feature, a contact can -be deleted directly from the table of all contacts, rather than requiring the user to navigate all the way to the edit view -of particular contact, in order to access the “Delete Contact” button we added in the last chapter.

-
-
-

Recall that we already have “Edit” and “View” links for each row, in the rows.html template:

-
-
-
The Existing Row Actions
-
-
<td>
+                        
+
+
+
    +
  1. +

    Change the hx-trigger to revealed

    +
  2. +
+
+
+

Now we have a truly lazy implementation, deferring the expensive computation until we are + absolutely sure we need it. A + pretty cool trick, and, again, a simple one-attribute change demonstrates the flexibility of + both htmx and the hypermedia + approach.

+
+
+
+
+

7.3. Inline Delete

+
+

For our next hypermedia trick, we are going to implement the “Inline Delete” pattern. + With this feature, a contact can + be deleted directly from the table of all contacts, rather than requiring the user to navigate + all the way to the edit view + of particular contact, in order to access the “Delete Contact” button we added in + the last chapter.

+
+
+

Recall that we already have “Edit” and “View” links for each row, in the + rows.html template:

+
+
+
The Existing Row Actions
+
+
<td>
     <a href="/contacts/{{ contact.id }}/edit">Edit</a>
     <a href="/contacts/{{ contact.id }}">View</a>
 </td>
-
-
-
-

Now we want to add a “Delete” link as well. And, thinking on it, we want that link to act an awful lot like the -“Delete Contact” button from edit.html, don’t we? We’d like to issue an HTTP DELETE to the URL for the given -contact and we want a confirmation dialog to ensure the user doesn’t accidentally delete a contact.

-
-
-

Here is the “Delete Contact” button html:

-
-
-
The Existing Row Actions
-
-
<button hx-delete="/contacts/{{ contact.id }}"
+                    
+
+
+

Now we want to add a “Delete” link as well. And, thinking on it, we want that link to + act an awful lot like the + “Delete Contact” button from edit.html, don’t we? We’d like + to issue an HTTP DELETE to the URL for the given + contact and we want a confirmation dialog to ensure the user doesn’t accidentally delete a + contact.

+
+
+

Here is the “Delete Contact” button html:

+
+
+
The Existing Row Actions
+
+
<button hx-delete="/contacts/{{ contact.id }}"
         hx-push-url="true"
         hx-confirm="Are you sure you want to delete this contact?"
         hx-target="body">
     Delete Contact
 </button>
-
-
-
-

As you may suspect by now, this is going to be another copy-and-paste job.

-
-
-

One thing to note is that, in the case of the “Delete Contact” button, we wanted to re-render the whole screen and update -the URL, since we are going to be returning from the edit view for the contact to the list view of all contacts. In -the case of this link, however, we are already on the list of contacts, so there is no need to update the URL, and -we can omit the hx-push-url attribute.

-
-
-

Here is the code for our inline “Delete” link:

-
-
-
The Existing Row Actions
-
-
<td>
+                    
+
+
+

As you may suspect by now, this is going to be another copy-and-paste job.

+
+
+

One thing to note is that, in the case of the “Delete Contact” button, we wanted to + re-render the whole screen and update + the URL, since we are going to be returning from the edit view for the contact to the list view + of all contacts. In + the case of this link, however, we are already on the list of contacts, so there is no need to + update the URL, and + we can omit the hx-push-url attribute.

+
+
+

Here is the code for our inline “Delete” link:

+
+
+
The Existing Row Actions
+
+
<td>
     <a href="/contacts/{{ contact.id }}/edit">Edit</a>
     <a href="/contacts/{{ contact.id }}">View</a>
     <a href="#" hx-delete="/contacts/{{ contact.id }}"
         hx-confirm="Are you sure you want to delete this contact?"
         hx-target="body">Delete</a> (1)
 </td>
-
-
-
-
    -
  1. -

    Almost a straight copy of the “Delete Contact” button

    -
  2. -
-
-
-

As you can see, we have added a new anchor tag and given it a blank target (the # value in its href attribute) to -retain the correct mouse-over styling behavior of the link. We’ve also copied the hx-delete, hx-confirm and -hx-target attributes from the “Delete Contact” button, but omitted the hx-push-url attributes since we don’t want -to update the URL of the browser.

-
-
-

We now have inline delete working, even with a confirmation dialog. A user can click on the “Delete” link and the -row will disappear from the UI as the entire page is re-rendered.

-
- -
-

Narrowing Our Target

-
-

We can get even fancier here, however. What if, rather than re-rendering the whole page, we just removed the row -for the contact? The user is looking at the row anyway, so is there really a need to re-render the whole page?

-
-
-

To do this, we’ll need to do a couple of things:

-
-
-
    -
  • -

    We’ll need to update this link to target the row that it is in

    -
  • -
  • -

    We’ll need to change the swap to outerHTML, since we want to replace (really, remove) the entire row

    -
  • -
  • -

    We’ll need to update the server side to render empty content when the DELETE is issued from a “Delete” link rather -than from the “Delete Contact” button on the contact edit page

    -
  • -
-
-
-

First things first, update the target of our “Delete” link to be the row that the link is in, rather than the entire -body. We can once again take advantage of the relative positional closest feature to target the closest tr, like -we did in our “Click To Load” and “Infinite Scroll” features:

-
-
-
The Existing Row Actions
-
-
<td>
+                    
+
+
+
    +
  1. +

    Almost a straight copy of the “Delete Contact” button

    +
  2. +
+
+
+

As you can see, we have added a new anchor tag and given it a blank target (the # + value in its href attribute) to + retain the correct mouse-over styling behavior of the link. We’ve also copied the + hx-delete, hx-confirm and + hx-target attributes from the “Delete Contact” button, but omitted the + hx-push-url attributes since we don’t want + to update the URL of the browser. +

+
+
+

We now have inline delete working, even with a confirmation dialog. A user can click on the + “Delete” link and the + row will disappear from the UI as the entire page is re-rendered.

+
+ +
+

Narrowing Our Target

+
+

We can get even fancier here, however. What if, rather than re-rendering the whole page, we + just removed the row + for the contact? The user is looking at the row anyway, so is there really a need to + re-render the whole page?

+
+
+

To do this, we’ll need to do a couple of things:

+
+
+
    +
  • +

    We’ll need to update this link to target the row that it is in

    +
  • +
  • +

    We’ll need to change the swap to outerHTML, since we want to + replace (really, remove) the entire row

    +
  • +
  • +

    We’ll need to update the server side to render empty content when the + DELETE is issued from a “Delete” link rather + than from the “Delete Contact” button on the contact edit page

    +
  • +
+
+
+

First things first, update the target of our “Delete” link to be the row that the + link is in, rather than the entire + body. We can once again take advantage of the relative positional closest + feature to target the closest tr, like + we did in our “Click To Load” and “Infinite Scroll” features:

+
+
+
The Existing Row Actions
+
+
<td>
     <a href="/contacts/{{ contact.id }}/edit">Edit</a>
     <a href="/contacts/{{ contact.id }}">View</a>
     <a href="#" hx-delete="/contacts/{{ contact.id }}"
@@ -8127,79 +12917,92 @@ we did in our “Click To Load” and “Infinite Scroll” feat
         hx-confirm="Are you sure you want to delete this contact?"
         hx-target="closest tr">Delete</a> (1)
 </td>
-
-
-
-
    -
  1. -

    Updated to target the closest enclosing tr (table row) of the link

    -
  2. -
-
-
-
-

Updating The Server Side

-
-

Now we need to update the server side as well. We want to keep the “Delete Contact” button working as well, and in -that case the current logic is correct. So we’ll need some way to differentiate between DELETE requests that are -triggered by the button and DELETE requests that come from this anchor.

-
-
-

The cleanest way to do this is to add an id attribute to the “Delete Contact” button, so that we can inspect the -HX-Trigger HTTP Request header to determine if the delete button was the cause of the request. This is a simple -change to the existing HTML:

-
-
-
Adding an id to the “Delete Contact” button
-
-
    <button id="delete-btn" (1)
+                        
+
+
+
    +
  1. +

    Updated to target the closest enclosing tr (table row) of the link

    +
  2. +
+
+
+
+

Updating The Server Side

+
+

Now we need to update the server side as well. We want to keep the “Delete + Contact” button working as well, and in + that case the current logic is correct. So we’ll need some way to differentiate + between DELETE requests that are + triggered by the button and DELETE requests that come from this anchor.

+
+
+

The cleanest way to do this is to add an id attribute to the “Delete + Contact” button, so that we can inspect the + HX-Trigger HTTP Request header to determine if the delete button was the cause + of the request. This is a simple + change to the existing HTML: +

+
+
+
Adding an id to the “Delete Contact” button
+
+
    <button id="delete-btn" (1)
             hx-delete="/contacts/{{ contact.id }}"
             hx-push-url="true"
             hx-confirm="Are you sure you want to delete this contact?"
             hx-target="body">
         Delete Contact
     </button>
-
-
-
-
    -
  1. -

    An id attribute has been added to the button

    -
  2. -
-
-
-

By giving this button an id attribute, we now have a mechanism for differentiating between the delete button in the -edit.html template and the delete links in the rows.html template. When this button issues a request, it will now -look something like this:

-
-
-
-
DELETE http://example.org/contacts/42 HTTP/1.1
+                        
+
+
+
    +
  1. +

    An id attribute has been added to the button

    +
  2. +
+
+
+

By giving this button an id attribute, we now have a mechanism for differentiating between + the delete button in the + edit.html template and the delete links in the rows.html template. + When this button issues a request, it will now + look something like this: +

+
+
+
+
DELETE http://example.org/contacts/42 HTTP/1.1
 Accept: text/html,*/*
 Host: example.org
 ...
 HX-Trigger: delete-btn
 ...
-
-
-
-

You can see that the request now includes the id of the button. This allows us to write code very similar to what we did -for the active search pattern, using a conditional on the HX-Trigger header to determine what we want to do. If that -header has the value delete-btn, then we know the request came from the button on the edit page, and we can do what we -are currently doing: delete the contact and redirect to /contacts page.

-
-
-

If it does not have that value, then we can simply delete the contact and return an empty string. This empty string -will replace the target, in this case the row for the given contact, thereby removing the row from the UI.

-
-
-

Let’s refactor our server-side code to do this:

-
-
-
Updating Our Server Code To Handle Two Different Delete Patterns
-
-
@app.route("/contacts/<contact_id>", methods=["DELETE"])
+                        
+
+
+

You can see that the request now includes the id of the button. This allows us + to write code very similar to what we did + for the active search pattern, using a conditional on the HX-Trigger header to + determine what we want to do. If that + header has the value delete-btn, then we know the request came from the button + on the edit page, and we can do what we + are currently doing: delete the contact and redirect to /contacts page.

+
+
+

If it does not have that value, then we can simply delete the contact and return an + empty string. This empty string + will replace the target, in this case the row for the given contact, thereby removing the + row from the UI.

+
+
+

Let’s refactor our server-side code to do this:

+
+
+
Updating Our Server Code To Handle Two Different Delete Patterns
+
+
@app.route("/contacts/<contact_id>", methods=["DELETE"])
 def contacts_delete(contact_id=0):
     contact = Contact.find(contact_id)
     contact.delete()
@@ -8208,140 +13011,175 @@ def contacts_delete(contact_id=0):
         return redirect("/contacts", 303)
     else:
         return "" (2)
-
-
-
-
    -
  1. -

    If the delete button on the edit page submitted this request, then continue to do the logic we had previous

    -
  2. -
  3. -

    If not, simply return an empty string, which will delete the row

    -
  4. -
-
-
-

And that’s our server-side implementation: when a user clicks “Delete” on a contact row and confirms the delete, the row will -disappear from the UI. Once again, we have a situation where just changing a few lines of simple code gives us a -dramatically different behavior. Hypermedia is very powerful in this manner.

-
-
-
-

The htmx Swapping Model

-
-

This is pretty cool, but there is another improvement we can make if we take some time to understand the htmx content -swapping model: it sure would be exciting if, rather than just instantly deleting the row, we faded it out before we removed -it. That easement makes it more obvious that the row is being removed, giving the user some nice visual feedback on the -deletion.

-
-
-

It turns out we can do this pretty easily with htmx, but to do so we’ll need to dig in to exactly how htmx swaps content.

-
- -
-
-

Taking Advantage of htmx-swapping

-
-

OK, so, let’s go back and look at our inline delete mechanic: we click an htmx enhanced link which deletes the contact -and then swaps some empty content in for the row. We know that, before the tr element is removed, it will have the -htmx-swapping class added to it. We can take advantage of that to write a CSS transition that fades the opacity of -the row to 0. Here is what that CSS looks like:

-
-
-
Adding A Fade Out Transition
-
-
tr.htmx-swapping { (1)
+                        
+
+
+
    +
  1. +

    If the delete button on the edit page submitted this request, then continue to do the + logic we had previous

    +
  2. +
  3. +

    If not, simply return an empty string, which will delete the row

    +
  4. +
+
+
+

And that’s our server-side implementation: when a user clicks “Delete” on a + contact row and confirms the delete, the row will + disappear from the UI. Once again, we have a situation where just changing a few lines of + simple code gives us a + dramatically different behavior. Hypermedia is very powerful in this manner.

+
+
+
+

The htmx Swapping Model

+
+

This is pretty cool, but there is another improvement we can make if we take some time to + understand the htmx content + swapping model: it sure would be exciting if, rather than just instantly deleting the row, + we faded it out before we removed + it. That easement makes it more obvious that the row is being removed, giving the user some + nice visual feedback on the + deletion.

+
+
+

It turns out we can do this pretty easily with htmx, but to do so we’ll need to dig in + to exactly how htmx swaps content.

+
+ +
+
+

Taking Advantage of htmx-swapping

+
+

OK, so, let’s go back and look at our inline delete mechanic: we click an htmx enhanced + link which deletes the contact + and then swaps some empty content in for the row. We know that, before the tr + element is removed, it will have the + htmx-swapping class added to it. We can take advantage of that to write a CSS + transition that fades the opacity of + the row to 0. Here is what that CSS looks like: +

+
+
+
Adding A Fade Out Transition
+
+
tr.htmx-swapping { (1)
   opacity: 0; (2)
   transition: opacity 1s ease-out; (3)
 }
-
-
-
-
    -
  1. -

    We want this style to apply to tr elements with the htmx-swapping class on them

    -
  2. -
  3. -

    The opacity will be 0, making it invisible

    -
  4. -
  5. -

    The opacity will transition to 0 over a 1 second time period, using the ease-out function

    -
  6. -
-
-
-

Again, this is not a CSS book and we are not going to go deeply into the details of CSS transitions, but hopefully the -above makes sense to you, even if this is the first time you’ve seen CSS transitions.

-
-
-

So, think about what this means from the htmx swapping model: when htmx gets content back to swap into the row it will -put the htmx-swapping class on the row and wait a bit. This will allow the transition to a zero opacity to occur, -fading the row out. Then the new (empty) content will be swapped in, which will effectively removing the row.

-
-
-

Sounds good, and we are nearly there. There is one more thing we need to do: the default “swap delay” for htmx is very -short, a few milliseconds. That makes sense in most cases: you don’t want to have much of a delay before you put the -new content into the DOM. But, in this case, we want to give the CSS animation time to complete before we do the swap, -we want to give it a second, in fact.

-
-
-

Fortunately htmx has an option for the hx-swap annotation that allows you to set the swap delay: following the swap -type you can add swap: followed by a timing value to tell htmx to wait a specific amount of time before it swaps. Let’s -update our HTML to allow a one second delay before the swap is done for the delete action:

-
-
-
The Existing Row Actions
-
-
<td>
+                        
+
+
+
    +
  1. +

    We want this style to apply to tr elements with the + htmx-swapping class on them

    +
  2. +
  3. +

    The opacity will be 0, making it invisible

    +
  4. +
  5. +

    The opacity will transition to 0 over a 1 second time period, using the + ease-out function

    +
  6. +
+
+
+

Again, this is not a CSS book and we are not going to go deeply into the details of CSS + transitions, but hopefully the + above makes sense to you, even if this is the first time you’ve seen CSS transitions. +

+
+
+

So, think about what this means from the htmx swapping model: when htmx gets content back to + swap into the row it will + put the htmx-swapping class on the row and wait a bit. This will allow the + transition to a zero opacity to occur, + fading the row out. Then the new (empty) content will be swapped in, which will effectively + removing the row.

+
+
+

Sounds good, and we are nearly there. There is one more thing we need to do: the default + “swap delay” for htmx is very + short, a few milliseconds. That makes sense in most cases: you don’t want to have much + of a delay before you put the + new content into the DOM. But, in this case, we want to give the CSS animation time to + complete before we do the swap, + we want to give it a second, in fact.

+
+
+

Fortunately htmx has an option for the hx-swap annotation that allows you to set + the swap delay: following the swap + type you can add swap: followed by a timing value to tell htmx to wait a + specific amount of time before it swaps. Let’s + update our HTML to allow a one second delay before the swap is done for the delete action: +

+
+
+
The Existing Row Actions
+
+
<td>
     <a href="/contacts/{{ contact.id }}/edit">Edit</a>
     <a href="/contacts/{{ contact.id }}">View</a>
     <a href="#" hx-delete="/contacts/{{ contact.id }}"
@@ -8349,140 +13187,171 @@ update our HTML to allow a one second delay before the swap is done for the dele
         hx-confirm="Are you sure you want to delete this contact?"
         hx-target="closest tr">Delete</a>
 </td>
-
-
-
-
    -
  1. -

    A swap delay changes how long htmx waits before it swaps in new content

    -
  2. -
-
-
-

With this modification, the existing row will stay in the DOM for an additional second, with the htmx-swapping class -on it. This will give the row time to transition to an opacity of zero, giving the fade out effect we want.

-
-
-

Now, when a user clicks on a “Delete” link and confirms the delete, the row will slowly fade out and then, once it has -faded to a 0 opacity, it will be removed. Pretty fancy, and all done in a declarative, hypermedia oriented manner, no -JavaScript required. (Well, obviously htmx is written in JavaScript, but you know what we mean: we didn’t have to write -any JavaScript to implement the feature.)

-
-
-
-
-

7.4. Bulk Delete

-
-

The final feature we are going to implement in this chapter is a “Bulk Delete”. The current mechanism for deleting users -is nice, but it would be annoying if a user wanted to delete five or ten contacts at a time, wouldn’t it? For the bulk -delete feature, we want to add the ability to select rows via a checkbox input and delete them all in a single go by clicking -a “Delete Selected Contacts” button.

-
-
-

To get started with this feature, we’ll need to add a checkbox input to each row in the rows.html template. This input -will have the name selected_contact_ids and its value will be the id of the contact for the current row.

-
-
-

Here is what the updated code for rows.html looks like:

-
-
-
Adding A Checkbox To Each Row
-
-
{% for contact in contacts %}
+                        
+
+
+
    +
  1. +

    A swap delay changes how long htmx waits before it swaps in new content

    +
  2. +
+
+
+

With this modification, the existing row will stay in the DOM for an additional second, with + the htmx-swapping class + on it. This will give the row time to transition to an opacity of zero, giving the fade out + effect we want.

+
+
+

Now, when a user clicks on a “Delete” link and confirms the delete, the row will + slowly fade out and then, once it has + faded to a 0 opacity, it will be removed. Pretty fancy, and all done in a declarative, + hypermedia oriented manner, no + JavaScript required. (Well, obviously htmx is written in JavaScript, but you know what we + mean: we didn’t have to write + any JavaScript to implement the feature.)

+
+
+
+
+

7.4. Bulk Delete

+
+

The final feature we are going to implement in this chapter is a “Bulk Delete”. The + current mechanism for deleting users + is nice, but it would be annoying if a user wanted to delete five or ten contacts at a time, + wouldn’t it? For the bulk + delete feature, we want to add the ability to select rows via a checkbox input and delete them + all in a single go by clicking + a “Delete Selected Contacts” button.

+
+
+

To get started with this feature, we’ll need to add a checkbox input to each row in the + rows.html template. This input + will have the name selected_contact_ids and its value will be the id + of the contact for the current row.

+
+
+

Here is what the updated code for rows.html looks like:

+
+
+
Adding A Checkbox To Each Row
+
+
{% for contact in contacts %}
 <tr>
   <td><input type="checkbox" name="selected_contact_ids" value="{{ contact.id }}"></td> (1)
   <td>{{ contact.first }}</td>
   ... omitted
 </tr>
 {% endfor %}
-
-
-
-
    -
  1. -

    A new cell with the checkbox input whose value is set to the current contact’s id

    -
  2. -
-
-
-

We’ll also need to add an empty column in the header for the table to accommodate the checkbox column. With that -done we now get a series of check boxes, one for each row, a pattern no doubt familiar to you from the web:

-
-
-
-screenshot checkboxes -
-
Figure 7. Checkboxes For Our Contact Rows
-
-
-

If you are not familiar with or have forgotten the way checkboxes work in HTML: a checkbox will submit its value associated -with the name of the input if and only if it is checked. So if, for example, you checked the contacts with the ids 3, -7 and 9, then those three values would all be submitted to the server. Since all the checkboxes in this case have -the same name, selected_contact_ids, all three values would be submitted with the name selected_contact_ids.

-
-
-

The “Delete Selected Contacts” button

-
-

The next step is to add a button below the table that will delete all the selected contacts. We want this button, like -our delete links in each row, to issue an HTTP DELETE, but rather than issuing it to the URL for a given contact, like -we do with the inline delete links and with the delete button on the edit page, here we want to issue the DELETE to -the /contacts URL.

-
-
-

As with the other delete elements, we want to confirm that the user wishes to delete the contacts, -and, for this case, we are going to target the body of page, since we are going to re-render the whole table.

-
-
-

Here is what the button code looks like:

-
-
-
The Delete Selected Contacts Button.
-
-
<button hx-delete="/contacts" (1)
+                    
+
+
+
    +
  1. +

    A new cell with the checkbox input whose value is set to the current contact’s id +

    +
  2. +
+
+
+

We’ll also need to add an empty column in the header for the table to accommodate the + checkbox column. With that + done we now get a series of check boxes, one for each row, a pattern no doubt familiar to you + from the web:

+
+
+
+ screenshot checkboxes +
+
Figure 7. Checkboxes For Our Contact Rows
+
+
+

If you are not familiar with or have forgotten the way checkboxes work in HTML: a checkbox will + submit its value associated + with the name of the input if and only if it is checked. So if, for example, you checked the + contacts with the ids 3, + 7 and 9, then those three values would all be submitted to the server. Since all the checkboxes + in this case have + the same name, selected_contact_ids, all three values would be submitted with the + name selected_contact_ids.

+
+
+

The “Delete Selected Contacts” button

+
+

The next step is to add a button below the table that will delete all the selected contacts. + We want this button, like + our delete links in each row, to issue an HTTP DELETE, but rather than issuing + it to the URL for a given contact, like + we do with the inline delete links and with the delete button on the edit page, here we want + to issue the DELETE to + the /contacts URL.

+
+
+

As with the other delete elements, we want to confirm that the user wishes to delete the + contacts, + and, for this case, we are going to target the body of page, since we are going to re-render + the whole table.

+
+
+

Here is what the button code looks like:

+
+
+
The Delete Selected Contacts Button.
+
+
<button hx-delete="/contacts" (1)
         hx-confirm="Are you sure you want to delete these contacts?" (2)
         hx-target="body"> (3)
     Delete Selected Contacts
 </button>
-
-
-
-
    -
  1. -

    Issue a DELETE to /contacts

    -
  2. -
  3. -

    Confirm that the user wants to delete the selected contacts

    -
  4. -
  5. -

    Target the body

    -
  6. -
-
-
-

Pretty easy. One question though: how are we going to include the values of all the selected checkboxes in the -request? As it stands right now, this is just a stand-alone button, and it doesn’t have any information indicating that -it should include any other information in the DELETE request it makes.

-
-
-

Fortunately, htmx has a few different ways to include values of inputs with a request.

-
-
-

One way would be to use the hx-include attribute, which allows you to use a CSS selector to specify the elements -you want to include in the request. That would work fine here, but we are going to use another approach that is a bit -simpler in this case.

-
-
-

By default, if an element is a child of a form element and makes a non-GET request, htmx will include all the values of -inputs within that form. In situations like this, where there is a bulk operation for a table, it is common to enclose -the whole table in a form tag, so that it is easy to add buttons that operate on the selected items.

-
-
-

Let’s add that form tag around the form, and be sure to enclose the button in it as well:

-
-
-
The Delete Selected Contacts Button.
-
-
    <form> (1)
+                        
+
+
+
    +
  1. +

    Issue a DELETE to /contacts

    +
  2. +
  3. +

    Confirm that the user wants to delete the selected contacts

    +
  4. +
  5. +

    Target the body

    +
  6. +
+
+
+

Pretty easy. One question though: how are we going to include the values of all the selected + checkboxes in the + request? As it stands right now, this is just a stand-alone button, and it doesn’t + have any information indicating that + it should include any other information in the DELETE request it makes.

+
+
+

Fortunately, htmx has a few different ways to include values of inputs with a request.

+
+
+

One way would be to use the hx-include attribute, which allows you to use a CSS + selector to specify the elements + you want to include in the request. That would work fine here, but we are going to use + another approach that is a bit + simpler in this case.

+
+
+

By default, if an element is a child of a form element and makes a + non-GET request, htmx will include all the values of + inputs within that form. In situations like this, where there is a bulk operation for a + table, it is common to enclose + the whole table in a form tag, so that it is easy to add buttons that operate on the + selected items.

+
+
+

Let’s add that form tag around the form, and be sure to enclose the button in it as + well:

+
+
+
The Delete Selected Contacts Button.
+
+
    <form> (1)
         <table>
           ... omitted
         </table>
@@ -8492,47 +13361,53 @@ the whole table in a form tag, so that it is easy to add buttons that operate on
             Delete Selected Contacts
         </button>
     </form> (2)
-
-
-
-
    -
  1. -

    The form tag encloses the entire table

    -
  2. -
  3. -

    And also encloses the button

    -
  4. -
-
-
-

Now, when the button issues a DELETE, it will include all the contact ids that have been selected as the -selected_contact_ids request variable.

-
-
-
-

The Server Side for Delete Selected Contacts

-
-

The server-side implementation is going to look an awful lot like our original server-side code for deleting a contact. -In fact, once again, we can just copy and paste, and fix a bit of stuff up:

-
-
-
    -
  • -

    We want to change the URL to /contacts

    -
  • -
  • -

    We want the handler to get all the ids submitted as selected_contact_ids and iterate over each one, deleting the -given contact

    -
  • -
-
-
-

Those are really the only changes we need to make! Here is what the server-side code looks like:

-
-
-
The Delete Selected Contacts Button.
-
-
@app.route("/contacts/", methods=["DELETE"]) (1)
+                        
+
+
+
    +
  1. +

    The form tag encloses the entire table

    +
  2. +
  3. +

    And also encloses the button

    +
  4. +
+
+
+

Now, when the button issues a DELETE, it will include all the contact ids that + have been selected as the + selected_contact_ids request variable. +

+
+
+
+

The Server Side for Delete Selected Contacts +

+
+

The server-side implementation is going to look an awful lot like our original server-side + code for deleting a contact. + In fact, once again, we can just copy and paste, and fix a bit of stuff up:

+
+
+
    +
  • +

    We want to change the URL to /contacts

    +
  • +
  • +

    We want the handler to get all the ids submitted as + selected_contact_ids and iterate over each one, deleting the + given contact

    +
  • +
+
+
+

Those are really the only changes we need to make! Here is what the server-side code looks + like:

+
+
+
The Delete Selected Contacts Button.
+
+
@app.route("/contacts/", methods=["DELETE"]) (1)
 def contacts_delete_all():
     contact_ids = list(map(int, request.form.getlist("selected_contact_ids"))) (2)
     for contact_id in contact_ids: (3)
@@ -8541,360 +13416,444 @@ def contacts_delete_all():
     flash("Deleted Contacts!") (5)
     contacts_set = Contact.all()
     return render_template("index.html", contacts=contacts_set)
-
-
-
-
    -
  1. -

    We handle a DELETE request to the /contacts/ path

    -
  2. -
  3. -

    We convert the selected_contact_ids values submitted to the server from a list of strings to a list integers

    -
  4. -
  5. -

    We iterate over all of the ids

    -
  6. -
  7. -

    And delete the given contact with each id

    -
  8. -
  9. -

    Beyond that, it’s the same code as our original delete handler: flash a message and render the index.html template

    -
  10. -
-
-
-

So, as you can see, we just took the original delete logic and slightly modified it to deal with an array of ids, rather -than a single id.

-
-
-

Readers with sharp eyes might notice one other small change: we did away with the redirect that was in the original -delete code. We did so because we are already on the page we want to re-render, so there is no reason -to redirect and have the URL update to something new. We can just re-render the page, and the new list of contacts (sans the -contacts that were deleted) will be re-rendered.

-
-
-

And there we go, we now have a bulk delete feature for our application. Once again, not a huge amount of code, and we -are implementing these features entirely by exchanging hypermedia with a server in the traditional, RESTful manner of -the web.

-
-
-
-
-
-
-

8. A Dynamic Archive UI

-
-
-

8.1. A Dynamic Archive UI

-
-

Contact.app has come a long way from the traditional web 1.0-style web application it started life as, at this point: -we’ve added active search, bulk delete (with some nice animations) and a slew of other features. We have reached a level -of interactivity that most web developers would assume requires some sort of Single-Page Application JavaScript framework, -but we’ve done nearly all of it using htmx-powered hypermedia instead.

-
-
-

Let’s look at how we can add a final significant feature to Contact.app: downloading an archive of all the contacts.

-
-
-

From a hypermedia perspective, downloading a file isn’t exactly rocket science: using the HTTP Content-Disposition -response header, we can easily tell the browser to download and save a file to the local computer.

-
-
-

However, let’s make this problem a bit more interesting: let’s add in the fact that the export can take a bit of time, -from five to ten seconds, or sometimes even longer, to complete.

-
-
-

This means that if we implemented the download as a “normal” HTTP request, driven by a link or a button, the user might -sit with very little visual feedback, wondering if the download is actually happening, while the export is being completed. -They might even give up in frustration and click the download hypermedia control again, causing a second archive -request. Not good.

-
-
-

This turns out to be a classic problem in web app development. When faced with potentially long-running process like this, -we ultimately have two options:

-
-
-
    -
  • -

    When the user triggers the action, block until it is complete and then respond with the result

    -
  • -
  • -

    Begin the action and return immediately, showing some sort of UI indicating that things are in progress

    -
  • -
-
-
-

Blocking and waiting for the action to complete is certainly the simpler way to handle it, but it can be a bad user -experience, especially if the action takes a while to complete. If you’ve ever clicked on something in a web 1.0-style -application and then had to sit there for what seems like an eternity before anything happens, you’ve seen the -practical results of this choice.

-
-
-

The second option, starting the action asynchronously (say, by creating a thread, or submitting it -to a job runner system) is much nicer from a user experience perspective: the server can respond immediately and the user -doesn’t need to sit there wondering what’s going on.

-
-
-

But the question is, what do you respond with? The job probably isn’t complete yet, so you can’t provide a link -to the results.

-
-
-

We have seen a few different “simple” approaches in this scenario in various web applications:

-
-
-
    -
  • -

    Let the user know that the process has started and that they will be emailed a link to the completed process -results when it is finished

    -
  • -
  • -

    Let the user know that the process has started and recommend that they should manually refresh the page to see the -status of the process

    -
  • -
  • -

    Let the user know that the process has started and automatically refresh the page every few seconds using some JavaScript

    -
  • -
-
-
-

All of these will work, but none of them is a great user experience.

-
-
-

What we’d really like in this scenario is something more like what you see when, for example, you download a large file via the -browser: a nice progress bar indicating where in the process you are, and, when the process is complete, a link to click immediately -to view the result of the process.

-
-
-

This may sound like something impossible to implement with hypermedia, and, to be honest, we’ll need to push htmx pretty hard -to make this all work, but, when it is done, it won’t be that much code, and we will be able to achieve the user experience -we want for this archiving feature.

-
-
-

UI Requirements

-
-

Before we dive into the implementation, let’s discuss in broad terms what our new UI should look like: we want a button -in the application labeled “Download Contact Archive”. When a user clicks on that button, we want to replace that -button with a UI that shows the progress of the archiving process, ideally with a progress bar. As the archive job makes -progress, we want to move the progress bar along towards completion. Then, when the archive job is done, we want to -show a link to the user to download the contact archive file.

-
-
-

In order to actually do the archiving, we are going to use a python class, Archiver, that implements all the -functionality that we need. As with the Contact class, we aren’t going to go into the implementation details of Archiver -, because that’s beyond the scope of this book. All you, the reader, need to know is that it provides all the server-side -behavior necessary to start a contact archive process and get the results when that process is done.

-
-
-

Archiver gives us the following methods to work with:

-
-
-
    -
  • -

    status() - A string representing the status of the download, either Waiting, Running or Complete

    -
  • -
  • -

    progress() - A number between 0 and 1, indicating how much progress the archive job has made

    -
  • -
  • -

    run() - Starts a new archive job (if the current status is Waiting)

    -
  • -
  • -

    reset() - Cancels the current archive job, if any, and resets to the “Waiting” state

    -
  • -
  • -

    archive_file() - The path to the archive file that has been created on the server, so we can send it to the client

    -
  • -
  • -

    get() - A class method that lets us get the Archiver for the current user

    -
  • -
-
-
-

Not a terribly sophisticated API.

-
-
-

The only somewhat tricky aspect to the whole API is that the run() method -is non-blocking. This means that it does not immediately create the archive file, but rather it starts a background job -(as a thread) to do the actual archiving. This can be confusing if you aren’t used to multithreading in code: you might -be expecting the run() method to “block”, that is, to actually execute the entire export and only return when it is -finished. But, if it did that, we wouldn’t be able to start the archive process and immediately render our desired -archive progress UI.

-
-
-
-

Beginning Our Implementation

-
-

We now have everything we need to begin implementing our UI: a reasonable outline of what it is going to look like, and -the domain logic to support it.

-
-
-

So, in getting down to building the UI, the first thing we want to note is that this UI is largely self-contained: we -want to replace the button with the download progress bar, and then the progress bar with a link to download the results -of the completed archive process.

-
-
-

The fact that our archive user interface is all going to be within a specific part of the UI is a strong hint -that we will want to create a new template to handle it. Let’s call this template archive_ui.html.

-
-
-

Another thing that jumps out at us is that we are going to want to replace the entire download UI in multiple cases:

-
-
-
    -
  • -

    When we start the download, we will want to replace the button with a progress bar

    -
  • -
  • -

    As the archive process proceeds, we will want to replace/update the progress bar

    -
  • -
  • -

    When the archive process completes, we will want to replace the progress bar with a download link

    -
  • -
-
-
-

Given we are going to be updating the UI in this way, it makes sense to have a good target for the updates. So, let’s -wrap the entire UI in a div tag, and then use that div as the target for all our operations.

-
-
-

Here is the start of the template content for our new archive user interface:

-
-
-
Our Initial Archive UI Template
-
-
<div id="archive-ui"
+                        
+
+
+
    +
  1. +

    We handle a DELETE request to the /contacts/ path

    +
  2. +
  3. +

    We convert the selected_contact_ids values submitted to the server from + a list of strings to a list integers

    +
  4. +
  5. +

    We iterate over all of the ids

    +
  6. +
  7. +

    And delete the given contact with each id

    +
  8. +
  9. +

    Beyond that, it’s the same code as our original delete handler: flash a message + and render the index.html template

    +
  10. +
+
+
+

So, as you can see, we just took the original delete logic and slightly modified it to deal + with an array of ids, rather + than a single id.

+
+
+

Readers with sharp eyes might notice one other small change: we did away with the redirect + that was in the original + delete code. We did so because we are already on the page we want to re-render, so there is + no reason + to redirect and have the URL update to something new. We can just re-render the page, and + the new list of contacts (sans the + contacts that were deleted) will be re-rendered.

+
+
+

And there we go, we now have a bulk delete feature for our application. Once again, not a + huge amount of code, and we + are implementing these features entirely by exchanging hypermedia with a server in the + traditional, RESTful manner of + the web.

+
+
+
+
+
+
+

8. A Dynamic Archive UI

+
+
+

8.1. A Dynamic Archive UI

+
+

Contact.app has come a long way from the traditional web 1.0-style web application it started + life as, at this point: + we’ve added active search, bulk delete (with some nice animations) and a slew of other + features. We have reached a level + of interactivity that most web developers would assume requires some sort of Single-Page + Application JavaScript framework, + but we’ve done nearly all of it using htmx-powered hypermedia instead.

+
+
+

Let’s look at how we can add a final significant feature to Contact.app: downloading an + archive of all the contacts.

+
+
+

From a hypermedia perspective, downloading a file isn’t exactly rocket science: using the + HTTP Content-Disposition + response header, we can easily tell the browser to download and save a file to the local + computer.

+
+
+

However, let’s make this problem a bit more interesting: let’s add in the fact that + the export can take a bit of time, + from five to ten seconds, or sometimes even longer, to complete.

+
+
+

This means that if we implemented the download as a “normal” HTTP request, driven by + a link or a button, the user might + sit with very little visual feedback, wondering if the download is actually happening, while the + export is being completed. + They might even give up in frustration and click the download hypermedia control again, causing + a second archive + request. Not good.

+
+
+

This turns out to be a classic problem in web app development. When faced with potentially + long-running process like this, + we ultimately have two options:

+
+
+
    +
  • +

    When the user triggers the action, block until it is complete and then respond with the + result

    +
  • +
  • +

    Begin the action and return immediately, showing some sort of UI indicating that things + are in progress

    +
  • +
+
+
+

Blocking and waiting for the action to complete is certainly the simpler way to handle it, but it + can be a bad user + experience, especially if the action takes a while to complete. If you’ve ever clicked on + something in a web 1.0-style + application and then had to sit there for what seems like an eternity before anything happens, + you’ve seen the + practical results of this choice.

+
+
+

The second option, starting the action asynchronously (say, by creating a thread, or submitting + it + to a job runner system) is much nicer from a user experience perspective: the server can respond + immediately and the user + doesn’t need to sit there wondering what’s going on.

+
+
+

But the question is, what do you respond with? The job probably isn’t complete + yet, so you can’t provide a link + to the results.

+
+
+

We have seen a few different “simple” approaches in this scenario in various web + applications:

+
+
+
    +
  • +

    Let the user know that the process has started and that they will be emailed a link to + the completed process + results when it is finished

    +
  • +
  • +

    Let the user know that the process has started and recommend that they should manually + refresh the page to see the + status of the process

    +
  • +
  • +

    Let the user know that the process has started and automatically refresh the page every + few seconds using some JavaScript

    +
  • +
+
+
+

All of these will work, but none of them is a great user experience.

+
+
+

What we’d really like in this scenario is something more like what you see when, + for example, you download a large file via the + browser: a nice progress bar indicating where in the process you are, and, when the process is + complete, a link to click immediately + to view the result of the process.

+
+
+

This may sound like something impossible to implement with hypermedia, and, to be honest, + we’ll need to push htmx pretty hard + to make this all work, but, when it is done, it won’t be that much code, and we + will be able to achieve the user experience + we want for this archiving feature.

+
+
+

UI Requirements

+
+

Before we dive into the implementation, let’s discuss in broad terms what our new UI + should look like: we want a button + in the application labeled “Download Contact Archive”. When a user clicks on + that button, we want to replace that + button with a UI that shows the progress of the archiving process, ideally with a progress + bar. As the archive job makes + progress, we want to move the progress bar along towards completion. Then, when the archive + job is done, we want to + show a link to the user to download the contact archive file.

+
+
+

In order to actually do the archiving, we are going to use a python class, + Archiver, that implements all the + functionality that we need. As with the Contact class, we aren’t going to + go into the implementation details of Archiver + , because that’s beyond the scope of this book. All you, the reader, need to know is + that it provides all the server-side + behavior necessary to start a contact archive process and get the results when that process + is done.

+
+
+

Archiver gives us the following methods to work with:

+
+
+
    +
  • +

    status() - A string representing the status of the download, either + Waiting, Running or Complete

    +
  • +
  • +

    progress() - A number between 0 and 1, indicating how much progress the + archive job has made

    +
  • +
  • +

    run() - Starts a new archive job (if the current status is + Waiting)

    +
  • +
  • +

    reset() - Cancels the current archive job, if any, and resets to the + “Waiting” state

    +
  • +
  • +

    archive_file() - The path to the archive file that has been created on + the server, so we can send it to the client

    +
  • +
  • +

    get() - A class method that lets us get the Archiver for the current + user

    +
  • +
+
+
+

Not a terribly sophisticated API.

+
+
+

The only somewhat tricky aspect to the whole API is that the run() method + is non-blocking. This means that it does not immediately create the + archive file, but rather it starts a background job + (as a thread) to do the actual archiving. This can be confusing if you aren’t used to + multithreading in code: you might + be expecting the run() method to “block”, that is, to actually + execute the entire export and only return when it is + finished. But, if it did that, we wouldn’t be able to start the archive process and + immediately render our desired + archive progress UI.

+
+
+
+

Beginning Our Implementation

+
+

We now have everything we need to begin implementing our UI: a reasonable outline of what it + is going to look like, and + the domain logic to support it.

+
+
+

So, in getting down to building the UI, the first thing we want to note is that this UI is + largely self-contained: we + want to replace the button with the download progress bar, and then the progress bar with a + link to download the results + of the completed archive process.

+
+
+

The fact that our archive user interface is all going to be within a specific part of the UI + is a strong hint + that we will want to create a new template to handle it. Let’s call this template + archive_ui.html.

+
+
+

Another thing that jumps out at us is that we are going to want to replace the entire + download UI in multiple cases:

+
+
+
    +
  • +

    When we start the download, we will want to replace the button with a progress bar +

    +
  • +
  • +

    As the archive process proceeds, we will want to replace/update the progress bar

    +
  • +
  • +

    When the archive process completes, we will want to replace the progress bar with a + download link

    +
  • +
+
+
+

Given we are going to be updating the UI in this way, it makes sense to have a good target + for the updates. So, let’s + wrap the entire UI in a div tag, and then use that div as the + target for all our operations.

+
+
+

Here is the start of the template content for our new archive user interface:

+
+
+
Our Initial Archive UI Template
+
+
<div id="archive-ui"
     hx-target="this" (1)
     hx-swap="outerHTML"> (2)
 </div>
-
-
-
-
    -
  1. -

    This div will be the target for all elements within it

    -
  2. -
  3. -

    Replace the entire div every time using outerHTML

    -
  4. -
-
-
-

Next, lets add the “Download Contact Archive” button to the div that will kick off the archive-then-download -process. We’ll use a POST to the path /contacts/archive to trigger the start of the archiving process:

-
-
-
Adding The Archive Button
-
-
<div id="archive-ui" hx-target="this" hx-swap="outerHTML">
+                        
+
+
+
    +
  1. +

    This div will be the target for all elements within it

    +
  2. +
  3. +

    Replace the entire div every time using outerHTML

    +
  4. +
+
+
+

Next, lets add the “Download Contact Archive” button to the div that + will kick off the archive-then-download + process. We’ll use a POST to the path /contacts/archive to + trigger the start of the archiving process:

+
+
+
Adding The Archive Button
+
+
<div id="archive-ui" hx-target="this" hx-swap="outerHTML">
   <button hx-post="/contacts/archive"> (1)
       Download Contact Archive
   </button>
 </div>
-
-
-
-
    -
  1. -

    This button will issue a POST to /contacts/archive

    -
  2. -
-
-
-

Finally, let’s include this new template in our main index.html template, above the contacts table:

-
-
-
Our Initial Archive UI Template
-
-
{% block content %}
+                        
+
+
+
    +
  1. +

    This button will issue a POST to /contacts/archive

    +
  2. +
+
+
+

Finally, let’s include this new template in our main index.html template, + above the contacts table:

+
+
+
Our Initial Archive UI Template
+
+
{% block content %}
 
     {% include 'archive_ui.html' %} (1)
 
     <form action="/contacts" method="get" class="tool-bar">
-
-
-
-
    -
  1. -

    This template will now be included in the main template

    -
  2. -
-
-
-

With that done, we now have a button showing up in our web application to get the download going. Since the enclosing -div has an hx-target="this" on it, the button will inherit that target and replace that enclosing div with whatever HTML -comes back from the POST to /contacts/archive.

-
-
-
-

Adding the Archiving End Point

-
-

Our next step is to handle the POST that our button is making. What we are going to want to do is to get the -Archiver for the current user and invoke the run() method on it. This will start the archive process running. Then -we will want to render some new content indicating that the process is running.

-
-
-

To do that, what we want to do is reuse the archive_ui template to handle rendering the archive UI for both states, -when the archiver is “Waiting” and when it is “Running”. (We will handle the “Complete” state in a bit.)

-
-
-

This is a very common pattern: we put all the different potential UIs for a given chunk of the user interface into -a single template, and conditionally render the appropriate interface. By keeping everything in one file, it makes -it much easier for other developers (or for us, if we come back after a while!) to understand exactly how the UI -works on the client side.

-
-
-

Since we are going to conditionally render different user interfaces based on the state of the archiver, we will need -to pass the archiver out to the template as a parameter. So, again: we need to invoke run() on the archiver in our -controller and then pass the archiver along to the template, so it can render the UI appropriate for the current -status of the archive process.

-
-
-

Here is what the code looks like:

-
-
-
Server Side Code To Start The Archive Process
-
-
@app.route("/contacts/archive", methods=["POST"]) (1)
+                        
+
+
+
    +
  1. +

    This template will now be included in the main template

    +
  2. +
+
+
+

With that done, we now have a button showing up in our web application to get the download + going. Since the enclosing + div has an hx-target="this" on it, the button will inherit that + target and replace that enclosing div with whatever HTML + comes back from the POST to /contacts/archive. +

+
+
+
+

Adding the Archiving End Point

+
+

Our next step is to handle the POST that our button is making. What we are going + to want to do is to get the + Archiver for the current user and invoke the run() method on it. + This will start the archive process running. Then + we will want to render some new content indicating that the process is running. +

+
+
+

To do that, what we want to do is reuse the archive_ui template to handle + rendering the archive UI for both states, + when the archiver is “Waiting” and when it is “Running”. (We will + handle the “Complete” state in a bit.)

+
+
+

This is a very common pattern: we put all the different potential UIs for a given chunk of + the user interface into + a single template, and conditionally render the appropriate interface. By keeping everything + in one file, it makes + it much easier for other developers (or for us, if we come back after a while!) to + understand exactly how the UI + works on the client side.

+
+
+

Since we are going to conditionally render different user interfaces based on the state of + the archiver, we will need + to pass the archiver out to the template as a parameter. So, again: we need to invoke + run() on the archiver in our + controller and then pass the archiver along to the template, so it can render the UI + appropriate for the current + status of the archive process.

+
+
+

Here is what the code looks like:

+
+
+
Server Side Code To Start The Archive Process
+
+
@app.route("/contacts/archive", methods=["POST"]) (1)
 def start_archive():
     archiver = Archiver.get() (2)
     archiver.run() (3)
     return render_template("archive_ui.html", archiver=archiver) (4)
-
-
-
-
    -
  1. -

    Handle POST to /contacts/archive

    -
  2. -
  3. -

    Look up the Archiver

    -
  4. -
  5. -

    Invoke the non-blocking run() method on it

    -
  6. -
  7. -

    Render the archive_ui.html template, passing in the archiver

    -
  8. -
-
-
-
-

Conditionally Rendering A Progress UI

-
-

Now let’s turn our attention to updating our archiving UI by setting archive_ui.html to conditionally render different -content depending on the state of the archive process. We are passing the archiver through -as a variable to the template, and recall that the archiver has a status() method that we can consult to see what -the status of the archive process is.

-
-
-

We want to render the “Download Contact Archive” button if the archiver has the status Waiting, and we want to render -some sort of message indicating that progress is happening if the status is Running. Let’s update our template code -to do just that:

-
-
-
Adding Conditional Rendering
-
-
<div id="archive-ui" hx-target="this" hx-swap="outerHTML">
+                        
+
+
+
    +
  1. +

    Handle POST to /contacts/archive

    +
  2. +
  3. +

    Look up the Archiver

    +
  4. +
  5. +

    Invoke the non-blocking run() method on it

    +
  6. +
  7. +

    Render the archive_ui.html template, passing in the archiver

    +
  8. +
+
+
+
+

Conditionally Rendering A Progress UI

+
+

Now let’s turn our attention to updating our archiving UI by setting + archive_ui.html to conditionally render different + content depending on the state of the archive process. We are passing the archiver through + as a variable to the template, and recall that the archiver has a status() + method that we can consult to see what + the status of the archive process is.

+
+
+

We want to render the “Download Contact Archive” button if the archiver has the + status Waiting, and we want to render + some sort of message indicating that progress is happening if the status is + Running. Let’s update our template code + to do just that:

+
+
+
Adding Conditional Rendering
+
+
<div id="archive-ui" hx-target="this" hx-swap="outerHTML">
     {% if archiver.status() == "Waiting" %} (1)
         <button hx-post="/contacts/archive">
             Download Contact Archive
@@ -8904,48 +13863,55 @@ to do just that:

{% end %} </div>
-
-
-
-
    -
  1. -

    Only render button if the status is “Waiting”

    -
  2. -
  3. -

    Render different content when status is “Running”

    -
  4. -
  5. -

    For now, just some text saying things are Running

    -
  6. -
-
-
-

OK, great, we have some conditional logic in our template view, and the server-side logic to support kicking off the -archive process. We don’t have a progress bar yet, but we’ll get there! Let’s see how this works as it stands, and -refresh the main page of our application…​

-
-
-
Something Went Wrong
-
-
UndefinedError
+                        
+
+
+
    +
  1. +

    Only render button if the status is “Waiting”

    +
  2. +
  3. +

    Render different content when status is “Running”

    +
  4. +
  5. +

    For now, just some text saying things are Running

    +
  6. +
+
+
+

OK, great, we have some conditional logic in our template view, and the server-side logic to + support kicking off the + archive process. We don’t have a progress bar yet, but we’ll get there! + Let’s see how this works as it stands, and + refresh the main page of our application…​

+
+
+
Something Went Wrong
+
+
UndefinedError
 jinja2.exceptions.UndefinedError: 'archiver' is undefined
-
-
-
-

Ouch!

-
-
-

We get an error message right out of the box. Why? Ah, of course, we are including the archive_ui.html in the -index.html template, but now the archive_ui.html template expects the archiver to be passed through to it, so -it can conditionally render the correct UI.

-
-
-

That’s an easy fix: we just need to pass the archiver through when we render the index.html template as well:

-
-
-
Including The Archiver When We Render index.html
-
-
@app.route("/contacts")
+                        
+
+
+

Ouch!

+
+
+

We get an error message right out of the box. Why? Ah, of course, we are including the + archive_ui.html in the + index.html template, but now the archive_ui.html template expects + the archiver to be passed through to it, so + it can conditionally render the correct UI. +

+
+
+

That’s an easy fix: we just need to pass the archiver through when we render the + index.html template as well:

+
+
+
Including The Archiver When We Render index.html
+
+
@app.route("/contacts")
 def contacts():
     search = request.args.get("q")
     if search is not None:
@@ -8955,125 +13921,148 @@ def contacts():
     else:
         contacts_set = Contact.all()
     return render_template("index.html", contacts=contacts_set, archiver=Archiver.get())(1)
-
-
-
-
    -
  1. -

    Pass through archiver to the main template

    -
  2. -
-
-
-

Now with that done, we can load up the page. And, sure enough, we can see the “Download Contact Archive” button.

-
-
-

When we click on it, the button is replaced with the content “Running…​”, and we can see in our development console -on the server-side that the job is indeed getting kicked off properly.

-
-
-
-
-

8.2. Polling

-
-

That’s definitely progress, but we don’t exactly have the best progress indicator here: just some static text telling -the user that the process is running.

-
-
-

Next we want to make the content update as the process makes progress and, ideally, show a progress bar indicating -how far along it is. How can we do that in htmx using plain old hypermedia?

-
-
-

The technique we want to use here is called “polling”, where we issue a request on an interval and update the UI based -on the new state of the server.

-
- -
-

htmx offers two types of polling. The first is “fixed rate polling”, which uses a special hx-trigger syntax to indicate -that something should be polled on a fixed interval.

-
-
-

Here is an example:

-
-
-
Fixed Interval Polling
-
-
<div hx-get="/messages" hx-trigger="every 3s"> (1)
+                        
+
+
+
    +
  1. +

    Pass through archiver to the main template

    +
  2. +
+
+
+

Now with that done, we can load up the page. And, sure enough, we can see the “Download + Contact Archive” button.

+
+
+

When we click on it, the button is replaced with the content + “Running…​”, and we can see in our development console + on the server-side that the job is indeed getting kicked off properly.

+
+
+
+
+

8.2. Polling

+
+

That’s definitely progress, but we don’t exactly have the best progress indicator + here: just some static text telling + the user that the process is running.

+
+
+

Next we want to make the content update as the process makes progress and, ideally, show a + progress bar indicating + how far along it is. How can we do that in htmx using plain old hypermedia?

+
+
+

The technique we want to use here is called “polling”, where we issue a request on an + interval and update the UI based + on the new state of the server.

+
+ +
+

htmx offers two types of polling. The first is “fixed rate polling”, which uses a + special hx-trigger syntax to indicate + that something should be polled on a fixed interval.

+
+
+

Here is an example:

+
+
+
Fixed Interval Polling
+
+
<div hx-get="/messages" hx-trigger="every 3s"> (1)
 </div>
-
-
-
-
    -
  1. -

    trigger a GET to /messages every three seconds

    -
  2. -
-
-
-

This works great in situations when you want to poll indefinitely, for example if you want to constantly poll for new -messages to display to the user. However, fixed rate polling isn’t ideal when you have a definite process after which -you want to stop polling: it keeps polling forever, until the element it is on is removed from the DOM.

-
-
-

In our case, we have a definite process with an ending to it. So, in our case, it will be better to use the second polling -technique, known as "load polling". In load polling, you take advantage of the fact that htmx triggers a load event -when content is loaded into the DOM. So you can create a trigger on the load event, but then add a bit of a delay so that -the request doesn’t trigger immediately.

-
-
-

When you do this, then you can conditionally render the hx-trigger on every request: when a process has completed you -can simply not include the load trigger and the load polling stops. A nice and simple way to poll until a definite -process finishes.

-
-
-

Using Polling To Update The Archive UI

-
-

Let’s use load polling to update our UI as the archiver makes progress. To show the progress, let’s use -a CSS-based progress bar, taking advantage of the progress() method which returns a number between 0 and 1 indicating -how close the archive process is to completion.

-
-
-

Here is the snippet of HTML we will use:

-
-
-
A CSS-based Progress Bar
-
-
<div class="progress" >
+                    
+
+
+
    +
  1. +

    trigger a GET to /messages every three seconds

    +
  2. +
+
+
+

This works great in situations when you want to poll indefinitely, for example if you want to + constantly poll for new + messages to display to the user. However, fixed rate polling isn’t ideal when you have a + definite process after which + you want to stop polling: it keeps polling forever, until the element it is on is removed from + the DOM.

+
+
+

In our case, we have a definite process with an ending to it. So, in our case, it will be better + to use the second polling + technique, known as "load polling". In load polling, you take advantage of the fact that htmx + triggers a load event + when content is loaded into the DOM. So you can create a trigger on the load event, + but then add a bit of a delay so that + the request doesn’t trigger immediately.

+
+
+

When you do this, then you can conditionally render the hx-trigger on every request: + when a process has completed you + can simply not include the load trigger and the load polling stops. A nice and + simple way to poll until a definite + process finishes.

+
+
+

Using Polling To Update The Archive UI

+
+

Let’s use load polling to update our UI as the archiver makes progress. To show the + progress, let’s use + a CSS-based progress bar, taking advantage of the progress() method which + returns a number between 0 and 1 indicating + how close the archive process is to completion.

+
+
+

Here is the snippet of HTML we will use:

+
+
+
A CSS-based Progress Bar
+
+
<div class="progress" >
     <div class="progress-bar" style="width:{{ archiver.progress() * 100 }}%"></div> (1)
 </div>
-
-
-
-
    -
  1. -

    The width of the inner element corresponds to the progress

    -
  2. -
-
-
-

This CSS-based progress bar has two components: an outer div that provides the wire frame for the progress bar, - and an inner div that is the actual progress bar indicator. We set the width of the inner progress bar to some percentage -(note we need to multiply the progress() result by 100 to get a percentage) and that will make the progress -indicator the appropriate width within the parent div.

-
-
-

As we have mentioned before, this is not a book on CSS, but, for completeness, here is the CSS for this progress bar:

-
-
-
The CSS For Our Progress Bar
-
-
.progress {
+                        
+
+
+
    +
  1. +

    The width of the inner element corresponds to the progress

    +
  2. +
+
+
+

This CSS-based progress bar has two components: an outer div that provides the + wire frame for the progress bar, + and an inner div that is the actual progress bar indicator. We set the width of + the inner progress bar to some percentage + (note we need to multiply the progress() result by 100 to get a percentage) and + that will make the progress + indicator the appropriate width within the parent div.

+
+
+

As we have mentioned before, this is not a book on CSS, but, for completeness, here is the + CSS for this progress bar:

+
+
+
The CSS For Our Progress Bar
+
+
.progress {
     height: 20px;
     margin-bottom: 20px;
     overflow: hidden;
@@ -9094,25 +14083,28 @@ indicator the appropriate width within the parent div.

box-shadow: inset 0 -1px 0 rgba(0,0,0,.15); transition: width .6s ease; }
-
-
-
-

Which ends up rendering like this:

-
-
-
-screenshot progress bar -
-
Figure 8. Our CSS-Based Progress Bar
-
-
-

Let’s add the code for our progress bar into our archive_ui.html template for the case when the archiver is -running, and let’s update the copy to say “Creating Archive…​”:

-
-
-
Adding The Progress Bar
-
-
<div id="archive-ui" hx-target="this" hx-swap="outerHTML">
+                        
+
+
+

Which ends up rendering like this:

+
+
+
+ screenshot progress bar +
+
Figure 8. Our CSS-Based Progress Bar
+
+
+

Let’s add the code for our progress bar into our archive_ui.html template + for the case when the archiver is + running, and let’s update the copy to say “Creating + Archive…​”:

+
+
+
Adding The Progress Bar
+
+
<div id="archive-ui" hx-target="this" hx-swap="outerHTML">
     {% if archiver.status() == "Waiting" %}
         <button hx-post="/contacts/archive">
             Download Contact Archive
@@ -9126,31 +14118,36 @@ running, and let’s update the copy to say “Creating Archive…&#
         </div>
     {% endif %}
 </div>
-
-
-
-
    -
  1. -

    Our shiny new progress bar

    -
  2. -
-
-
-

Now when we click the “Download Contact Archive” button, we get the progress bar. But it still doesn’t update -because we haven’t implemented load polling yet: it just sits there, at zero.

-
-
-

To get the progress bar updating dynamically, we’ll need to implement load polling using hx-trigger. We can add this -to pretty much any element inside the conditional block for when the archiver is running, so let’s add it to that div that is -wrapping around the “Creating Archive…​” text and the progress bar.

-
-
-

Let’s make it poll by issuing an HTTP GET to the same path that the POST was issued too: /contacts/archive.

-
-
-
Implementing Load Polling
-
-
<div id="archive-ui" hx-target="this" hx-swap="outerHTML">
+                        
+
+
+
    +
  1. +

    Our shiny new progress bar

    +
  2. +
+
+
+

Now when we click the “Download Contact Archive” button, we get the progress bar. + But it still doesn’t update + because we haven’t implemented load polling yet: it just sits there, at zero.

+
+
+

To get the progress bar updating dynamically, we’ll need to implement load polling + using hx-trigger. We can add this + to pretty much any element inside the conditional block for when the archiver is running, so + let’s add it to that div that is + wrapping around the “Creating Archive…​” text and the progress bar. +

+
+
+

Let’s make it poll by issuing an HTTP GET to the same path that the + POST was issued too: /contacts/archive.

+
+
+
Implementing Load Polling
+
+
<div id="archive-ui" hx-target="this" hx-swap="outerHTML">
     {% if archiver.status() == "Waiting" %}
         <button hx-post="/contacts/archive">
             Download Contact Archive
@@ -9164,69 +14161,82 @@ wrapping around the “Creating Archive…​” text and the pr
         </div>
     {% endif %}
 </div>
-
-
-
-
    -
  1. -

    Issue a GET to /contacts/archive 500 milliseconds after the content loads

    -
  2. -
-
-
-

Again, it is important to realize that, when this GET is issued to /contacts/archive, it is going to replace -the div with the id archive-ui, not just itself. The hx-target attribute on the div with the id archive-ui is -inherited by all child elements within that div, so the children will all target that outermost div in the -archive_ui.html file.

-
-
-

Now we need to handle the GET to /contacts/archive on the server. Thankfully, this is quite easy: all we -want to do is re-render archive_ui.html with the archiver:

-
-
-
Handling Progress Updates
-
-
@app.route("/contacts/archive", methods=["GET"]) (1)
+                        
+
+
+
    +
  1. +

    Issue a GET to /contacts/archive 500 milliseconds after the + content loads

    +
  2. +
+
+
+

Again, it is important to realize that, when this GET is issued to + /contacts/archive, it is going to replace + the div with the id archive-ui, not just itself. The + hx-target attribute on the div with the id archive-ui + is + inherited by all child elements within that div, so the children will + all target that outermost div in the + archive_ui.html file. +

+
+
+

Now we need to handle the GET to /contacts/archive on the server. + Thankfully, this is quite easy: all we + want to do is re-render archive_ui.html with the archiver:

+
+
+
Handling Progress Updates
+
+
@app.route("/contacts/archive", methods=["GET"]) (1)
 def archive_status():
     archiver = Archiver.get()
     return render_template("archive_ui.html", archiver=archiver) (2)
-
-
-
-
    -
  1. -

    handle GET to the /contacts/archive path

    -
  2. -
  3. -

    just re-render the archive_ui.html template

    -
  4. -
-
-
-

Simple, like so much else with hypermedia.

-
-
-

Now, when we click the “Download Contact Archive”, sure enough, we get a progress bar that updates every 500 -milliseconds. And, as the result of the call to archiver.progress() incrementally updates from 0 to 1, the -progress bar moves across the screen for us. Very cool.

-
-
-
-

Downloading The Result

-
-

We have one final state to handle, the case when achiver.status() is set to “Complete”, and there is a JSON -archive of the data ready to download. When the archiver is complete, we can get the local JSON file on the server -from the archiver via the archive_file() call.

-
-
-

Let’s add another case to our if statement to handle the “Complete” state, and, when the archive job is complete, lets -render a link to a new path, /contacts/archive/file, which will respond with the archived JSON file. Here is -the new code:

-
-
-
Rendering A Download Link When Archiving Completes
-
-
<div id="archive-ui" hx-target="this" hx-swap="outerHTML">
+                        
+
+
+
    +
  1. +

    handle GET to the /contacts/archive path

    +
  2. +
  3. +

    just re-render the archive_ui.html template

    +
  4. +
+
+
+

Simple, like so much else with hypermedia.

+
+
+

Now, when we click the “Download Contact Archive”, sure enough, we get a progress + bar that updates every 500 + milliseconds. And, as the result of the call to archiver.progress() + incrementally updates from 0 to 1, the + progress bar moves across the screen for us. Very cool.

+
+
+
+

Downloading The Result

+
+

We have one final state to handle, the case when achiver.status() is set to + “Complete”, and there is a JSON + archive of the data ready to download. When the archiver is complete, we can get the local + JSON file on the server + from the archiver via the archive_file() call.

+
+
+

Let’s add another case to our if statement to handle the “Complete” state, + and, when the archive job is complete, lets + render a link to a new path, /contacts/archive/file, which will respond with + the archived JSON file. Here is + the new code:

+
+
+
Rendering A Download Link When Archiving Completes
+
+
<div id="archive-ui" hx-target="this" hx-swap="outerHTML">
     {% if archiver.status() == "Waiting" %}
         <button hx-post="/contacts/archive">
             Download Contact Archive
@@ -9242,499 +14252,610 @@ the new code:

<a hx-boost="false" href="/contacts/archive/file">Archive Ready! Click here to download. &downarrow;</a> (2) {% endif %} </div>
-
-
-
-
    -
  1. -

    If the status is “Complete”, render a download link

    -
  2. -
  3. -

    The link will issue a GET to /contacts/archive/file

    -
  4. -
-
-
-

Note that the link has a hx-boost set to false. It has this so that the link will not inherit the boost behavior -that is present for other links and, thus, will not be issued via AJAX. We want this “normal” link behavior because an -AJAX request cannot download a file directly, whereas a plain anchor tag can.

-
-
-
-

Downloading The Completed Archive

-
-

The final step is to handle the GET request to /contacts/archive/file. We want to send the file that the -archiver created down to the client. We are in luck: Flask has a very simple mechanism for sending a file as -a downloaded response: the send_file() method.

-
-
-

We can pass this method the path to the archive file that the archiver -created, the name of the file that we want the browser to create, and if we want it sent “as an attachment”. -This last argument will which will tell Flask to set the HTTP response header Content-Disposition to attachment -with the given filename, which will trigger the browsers file-downloading behavior.

-
-
-
Sending A File To The Client
-
-
@app.route("/contacts/archive/file", methods=["GET"])
+                        
+
+
+
    +
  1. +

    If the status is “Complete”, render a download link

    +
  2. +
  3. +

    The link will issue a GET to /contacts/archive/file

    +
  4. +
+
+
+

Note that the link has a hx-boost set to false. It has this so that + the link will not inherit the boost behavior + that is present for other links and, thus, will not be issued via AJAX. We want this + “normal” link behavior because an + AJAX request cannot download a file directly, whereas a plain anchor tag can.

+
+
+
+

Downloading The Completed Archive

+
+

The final step is to handle the GET request to + /contacts/archive/file. We want to send the file that the + archiver created down to the client. We are in luck: Flask has a very simple mechanism for + sending a file as + a downloaded response: the send_file() method.

+
+
+

We can pass this method the path to the archive file that the archiver + created, the name of the file that we want the browser to create, and if we want it sent + “as an attachment”. + This last argument will which will tell Flask to set the HTTP response header + Content-Disposition to attachment + with the given filename, which will trigger the browsers file-downloading behavior.

+
+
+
Sending A File To The Client
+
+
@app.route("/contacts/archive/file", methods=["GET"])
 def archive_content():
     manager = Archiver.get()
     return send_file(manager.archive_file(), "archive.json", as_attachment=True) (1)
-
-
-
-
    -
  1. -

    send the file to the client

    -
  2. -
-
-
-

Perfect. Now we have an archive UI that is very slick. You click the “Download Contacts Archive” button and a progress -bar appears. When the progress bar reaches 100%, it disappears and a link to download the archive file appears. The user -can then click on that link and download their archive. A nice, polished user experience when compared with the common -click-and-wait experience of many websites.

-
-
-
-
-

8.3. Smoothing Things Out: Animations in htmx

-
-

As nice as this UI is, there is one minor annoyance with it: as the progress bar updates it “jumps” from one position -to the next. This looks jerky and is reminiscent of the feel of a full page refresh in web 1.0 style applications. It -turns out that there is a native HTML technology for smoothing out changes on an element from one state to another -that we discussed in Chapter 5: the CSS Transitions API.

-
-
-

Using CSS Transitions, you can smoothly animate an element between different styling by using the transition property.

-
-
-

If you look back at our CSS definition of the .progress-bar class, you will see the following transition definition -in it: transition: width .6s ease;. This means that when the width of the progress bar is changed from, say 20% to -30%, the browser will animate over a period of .6 seconds using the “ease” function (which has a nice accelerate/decelerate -effect).

-
-
-

Unfortunately that nice transition isn’t being applied in our current UI. This is because, in our example, htmx is -replacing the progress bar with new one every time it polls. It isn’t updating the width -of the existing element. CSS transitions, unfortunately, only apply when the properties of an existing element change, -not when the element is replaced. This is a reason why pure HTML-based applications can feel jerky and unpolished when compared -with their SPA counterparts: it is hard to use CSS transitions without using some JavaScript.

-
-
-

This is unfortunate, but htmx rectifies this situation with its swapping model. Let’s look at how.

-
-
-

The “Settling” Step in htmx

-
-

When we discussed the htmx swap model in Chapter 5, we focused on the classes that htmx adds and removes, but we skipped -over the idea of “settling”. What is “settling” in htmx terms? Settling is the following process: when htmx is -about to replace a chunk of content, it looks through the new content and finds all elements with an id on it. It then -looks in the existing content for elements with the same id. If there is one, it does the following shuffle:

-
-
-
    -
  • -

    The new content gets the attributes of the old content temporarily

    -
  • -
  • -

    The new content is inserted

    -
  • -
  • -

    After a small delay, the new content has its attributes reverted to their actual values

    -
  • -
-
-
-

So, what is this strange little dance supposed to achieve? Well, what this ends up meaning is that, if an element -has a stable id between swaps, you can write CSS transitions between various states. Since the new content briefly -has the old attributes, the normal CSS mechanism will kick in when the actual values are restored.

-
-
-

So, in our case, all we need to do is to add a stable ID to our progress-bar element, and, rather than jumping -on every update, the progress bar should smoothly move across the screen as it is updating, using the CSS transition -defined in our style sheet:

-
-
-
Smoothing Things Out
-
-
<div class="progress" >
+                        
+
+
+
    +
  1. +

    send the file to the client

    +
  2. +
+
+
+

Perfect. Now we have an archive UI that is very slick. You click the “Download Contacts + Archive” button and a progress + bar appears. When the progress bar reaches 100%, it disappears and a link to download the + archive file appears. The user + can then click on that link and download their archive. A nice, polished user experience + when compared with the common + click-and-wait experience of many websites.

+
+
+
+
+

8.3. Smoothing Things Out: Animations in htmx

+
+

As nice as this UI is, there is one minor annoyance with it: as the progress bar updates it + “jumps” from one position + to the next. This looks jerky and is reminiscent of the feel of a full page refresh in web 1.0 + style applications. It + turns out that there is a native HTML technology for smoothing out changes on an element from + one state to another + that we discussed in Chapter 5: the CSS Transitions API.

+
+
+

Using CSS Transitions, you can smoothly animate an element between different styling by using the + transition property.

+
+
+

If you look back at our CSS definition of the .progress-bar class, you will see the + following transition definition + in it: transition: width .6s ease;. This means that when the width of the progress + bar is changed from, say 20% to + 30%, the browser will animate over a period of .6 seconds using the “ease” function + (which has a nice accelerate/decelerate + effect).

+
+
+

Unfortunately that nice transition isn’t being applied in our current UI. This is because, + in our example, htmx is + replacing the progress bar with new one every time it polls. It isn’t updating + the width + of the existing element. CSS transitions, unfortunately, only apply when the properties + of an existing element change, + not when the element is replaced. This is a reason why pure HTML-based applications can feel + jerky and unpolished when compared + with their SPA counterparts: it is hard to use CSS transitions without using some JavaScript. +

+
+
+

This is unfortunate, but htmx rectifies this situation with its swapping model. Let’s look + at how.

+
+
+

The “Settling” Step in htmx

+
+

When we discussed the htmx swap model in Chapter 5, we focused on the classes that htmx adds + and removes, but we skipped + over the idea of “settling”. What is “settling” in htmx terms? + Settling is the following process: when htmx is + about to replace a chunk of content, it looks through the new content and finds all elements + with an id on it. It then + looks in the existing content for elements with the same id. If there + is one, it does the following shuffle:

+
+
+
    +
  • +

    The new content gets the attributes of the old content temporarily +

    +
  • +
  • +

    The new content is inserted

    +
  • +
  • +

    After a small delay, the new content has its attributes reverted to their actual + values

    +
  • +
+
+
+

So, what is this strange little dance supposed to achieve? Well, what this ends up meaning is + that, if an element + has a stable id between swaps, you can write CSS transitions between various + states. Since the new content briefly + has the old attributes, the normal CSS mechanism will kick in when the actual + values are restored.

+
+
+

So, in our case, all we need to do is to add a stable ID to our progress-bar + element, and, rather than jumping + on every update, the progress bar should smoothly move across the screen as it is updating, + using the CSS transition + defined in our style sheet:

+
+
+
Smoothing Things Out
+
+
<div class="progress" >
     <div id="archive-progress" class="progress-bar" style="width:{{ archiver.progress() * 100 }}%"></div> (1)
 </div>
-
-
-
-
    -
  1. -

    The progress bar div now has a stable id across requests

    -
  2. -
-
-
-

So, despite all the complicated mechanics going on behind the scenes in htmx, all we have to do, as an htmx user, -is add a stable id attribute to the element we want to animate.

-
-
-

With that done, voila: we get a nice, smooth progress bar as the contact archiving process proceeds. Because of -the htmx swapping model, we get this nice animation even though we are replacing the content with new HTML. So we get -the simplicity of the HTML-based approach, but the look and feel of a more sophisticated JavaScript-based approach.

-
-
-
-
-

8.4. Dismissing The Download UI

-
-

Next, let’s make it possible for the user to dismiss the download link and return to the original export UI state. To -do this, we’ll add a button that issues a DELETE to the path /contacts/archive, indicating that the current archive -can be removed or cleaned up.

-
-
-

We’ll add it after the download link, like so:

-
-
-
Clearing The Download
-
-
    <a hx-boost="false" href="/contacts/archive/file" _="on load click() me">Archive Ready!  Click here to download. &downarrow;</a>
+                        
+
+
+
    +
  1. +

    The progress bar div now has a stable id across requests

    +
  2. +
+
+
+

So, despite all the complicated mechanics going on behind the scenes in htmx, all we have to + do, as an htmx user, + is add a stable id attribute to the element we want to animate.

+
+
+

With that done, voila: we get a nice, smooth progress bar as the contact archiving process + proceeds. Because of + the htmx swapping model, we get this nice animation even though we are replacing the content + with new HTML. So we get + the simplicity of the HTML-based approach, but the look and feel of a more sophisticated + JavaScript-based approach.

+
+
+
+
+

8.4. Dismissing The Download UI

+
+

Next, let’s make it possible for the user to dismiss the download link and return to the + original export UI state. To + do this, we’ll add a button that issues a DELETE to the path + /contacts/archive, indicating that the current archive + can be removed or cleaned up.

+
+
+

We’ll add it after the download link, like so:

+
+
+
Clearing The Download
+
+
    <a hx-boost="false" href="/contacts/archive/file" _="on load click() me">Archive Ready!  Click here to download. &downarrow;</a>
     <button hx-delete="/contacts/archive">Clear Download</button> (1)
-
-
-
-
    -
  1. -

    A simple button that issues a DELETE to /contacts/archive

    -
  2. -
-
-
-

Now the user has a button that they can click on to dismiss the archive download link. But we will need to hook it up -on the server side. As usual, this is pretty straight forward: we simply create a new handler for the DELETE HTTP Action, -invoke the reset() method on the archiver, and re-render the archive_ui.html template.

-
-
-

Since this button is picking up the same hx-target and hx-swap configuration as everything else, it “just works”.

-
-
-

Here is the server-side code:

-
-
-
Resetting The Download
-
-
@app.route("/contacts/archive", methods=["DELETE"])
+                    
+
+
+
    +
  1. +

    A simple button that issues a DELETE to /contacts/archive

    +
  2. +
+
+
+

Now the user has a button that they can click on to dismiss the archive download link. But we + will need to hook it up + on the server side. As usual, this is pretty straight forward: we simply create a new handler + for the DELETE HTTP Action, + invoke the reset() method on the archiver, and re-render the + archive_ui.html template.

+
+
+

Since this button is picking up the same hx-target and hx-swap + configuration as everything else, it “just works”.

+
+
+

Here is the server-side code:

+
+
+
Resetting The Download
+
+
@app.route("/contacts/archive", methods=["DELETE"])
 def reset_archive():
     archiver = Archiver.get()
     archiver.reset() (1)
     return render_template("archive_ui.html", archiver=archiver)
-
-
-
-
    -
  1. -

    Call reset() on the archiver

    -
  2. -
-
-
-

This looks pretty similar to our other handlers, doesn’t it? Yep, that’s the idea.

-
-
-
-

8.5. An Alternative UX: Auto-Download

-
-

While we prefer the current user experience for archiving contacts, where a progress bar shows the progress of -the process and, when it completes, the user is presented with a link to actually download the file, there are other -alternatives to it. Another pattern that we see on the web is "auto-downloading", where the file downloads immediately -without the user needing to click a link.

-
-
-

We can add this functionality quite easily to our application with just a bit of scripting. We will discuss scripting -in a Hypermedia-Driven Application in more depth in a few chapters, but as a quick introduction: scripting is perfectly -acceptable in an HDA, as long as it doesn’t replace the core hypermedia mechanics of the application.

-
-
-

For our auto-download feature we will use _hyperscript , our preferred scripting option, but the -equivalent JavaScript would be nearly as simple.

-
-
-

All we need to do to implement the auto-download feature is the following: when the download link renders, - automatically click on the link for the user.

-
-
-

The _hyperscript code reads almost the same as the previous sentence (which is why we love hyperscript):

-
-
-
Auto-Downloading
-
-
  <a hx-boost="false" href="/contacts/archive/file"
+                    
+
+
+
    +
  1. +

    Call reset() on the archiver

    +
  2. +
+
+
+

This looks pretty similar to our other handlers, doesn’t it? Yep, that’s the idea. +

+
+
+
+

8.5. An Alternative UX: Auto-Download

+
+

While we prefer the current user experience for archiving contacts, where a progress bar shows + the progress of + the process and, when it completes, the user is presented with a link to actually download the + file, there are other + alternatives to it. Another pattern that we see on the web is "auto-downloading", where the file + downloads immediately + without the user needing to click a link.

+
+
+

We can add this functionality quite easily to our application with just a bit of scripting. We + will discuss scripting + in a Hypermedia-Driven Application in more depth in a few chapters, but as a quick introduction: + scripting is perfectly + acceptable in an HDA, as long as it doesn’t replace the core hypermedia mechanics of the + application.

+
+
+

For our auto-download feature we will use _hyperscript , + our preferred scripting option, but the + equivalent JavaScript would be nearly as simple.

+
+
+

All we need to do to implement the auto-download feature is the following: when the download link + renders, + automatically click on the link for the user.

+
+
+

The _hyperscript code reads almost the same as the previous sentence (which is why we love + hyperscript):

+
+
+
Auto-Downloading
+
+
  <a hx-boost="false" href="/contacts/archive/file"
      _="on load click() me"> (1)
     Archive Downloading!  Click here if the download does not start.
   </a>
-
-
-
-
    -
  1. -

    a bit of _hyperscript to make the file auto-download

    -
  2. -
-
-
-

Have we mentioned that we love _hyperscript?

-
-
-

Crucially, the scripting here is simply enhancing the existing hypermedia, rather than replacing it with -a non-hypermedia request. This is hypermedia-friendly scripting, as we will cover in more depth in a bit.

-
-
-
-

8.6. A Dynamic Archive UI: Complete

-
-

In this chapter we’ve managed to create a very dynamic UI for our contact archive functionality, with a progress bar and -auto-downloading, and we’ve done nearly all of it (with the exception of a small bit of scripting for auto-download) in -pure hypermedia. And it only took about 16 lines of front end code and 16 lines of backend code to build the whole thing.

-
-
-

This shows once again that HTML, with a bit of help from htmx, can in fact be extremely powerful and expressive.

-
-
-
-
-
-

9. Tricks Of The htmx Masters

-
-
-

Advanced 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 of the more advanced attributes in htmx, as well as expand on the advanced details of some attributes we -have already used.

-
-
-

Additionally, we will look at 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.

-
-
-

By understanding all the features and techniques in this chapter, you will be able to pull off extremely -sophisticated user interfaces using only htmx and perhaps a small bit of hypermedia-friendly client-side scripting.

-
-
-
-

9.1. htmx Attributes

-
-

Thus far we have used about fifteen different attributes from htmx in our application. The most important ones -have been:

-
-
-
-
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 6, 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:

-
-
-
-
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
-
-
<button hx-get="/contacts" hx-target="#content-div"
+                    
+
+
+
    +
  1. +

    a bit of _hyperscript to make the file auto-download

    +
  2. +
+
+
+

Have we mentioned that we love _hyperscript?

+
+
+

Crucially, the scripting here is simply enhancing the existing hypermedia, rather than + replacing it with + a non-hypermedia request. This is hypermedia-friendly scripting, as we will cover in more depth + in a bit.

+
+
+
+

8.6. A Dynamic Archive UI: Complete

+
+

In this chapter we’ve managed to create a very dynamic UI for our contact archive + functionality, with a progress bar and + auto-downloading, and we’ve done nearly all of it (with the exception of a small bit of + scripting for auto-download) in + pure hypermedia. And it only took about 16 lines of front end code and 16 lines of backend code + to build the whole thing.

+
+
+

This shows once again that HTML, with a bit of help from htmx, can in fact be extremely powerful + and expressive.

+
+
+
+
+
+

9. Tricks Of The htmx Masters

+
+
+

Advanced 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 of the more advanced attributes in htmx, as well as expand on the advanced + details of some attributes we + have already used.

+
+
+

Additionally, we will look at 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.

+
+
+

By understanding all the features and techniques in this chapter, you will be able to pull off + extremely + sophisticated user interfaces using only htmx and perhaps a small bit of hypermedia-friendly + client-side scripting.

+
+
+
+

9.1. htmx Attributes

+
+

Thus far we have used about fifteen different attributes from htmx in our application. The most + important ones + have been:

+
+
+
+
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 6, 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:

+
+
+
+
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
+
+
<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

    -
  2. -
-
-
-

More details and examples can be found online in the hx-swap documentation.

-
-
-
-

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
-
-
    <input id="search" type="search" name="q" value="{{ request.args.get('q') or '' }}"
+                        
+
+
+
    +
  1. +

    This tells htmx to show the top of the body after the swap occurs

    +
  2. +
+
+
+

More details and examples can be found online in the hx-swap documentation.

+
+
+
+

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
+
+
    <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

    -
  2. -
-
-
-

This example took advantage of two modifiers available for the hx-trigger attribute:

-
-
-
-
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:

-
-
-
-
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 for 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
-
-
<script>
+                        
+
+
+
    +
  1. +

    An elaborate trigger specification

    +
  2. +
+
+
+

This example took advantage of two modifiers available for the hx-trigger + attribute:

+
+
+
+
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:

+
+
+
+
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 for 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
+
+
<script>
   function contactRetrievalEnabled() {
       // code to test if contact retrieval is enabled
       ...
@@ -9743,123 +14864,151 @@ an event filter in htmx, you would write the following HTML:

<button hx-get="/contacts" hx-trigger="click[contactRetrievalEnabled()]"> (1) Get Contacts </button>
-
-
-
-
    -
  1. -

    A request is issued on click only when contactRetrievalEnabled() returns true

    -
  2. -
-
-
-

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 -at MDN.

-
-
-

Intersection gives you fine grained control over exactly when a request should be triggered. For example, you can -set a threshold and specify that the request be issued only when an element is 50% visible.

-
-
-

The hx-trigger attribute certainly is the most complex in htmx. More details and examples can be found in its documentation.

-
-
-
-
-

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:

-
-
-
-
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
-
-
<button hx-get="/contacts" hx-target="body"> (1)
+                            
+
+
+
    +
  1. +

    A request is issued on click only when contactRetrievalEnabled() + returns true

    +
  2. +
+
+
+

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 + at MDN. +

+
+
+

Intersection gives you fine grained control over exactly when a request should be + triggered. For example, you can + set a threshold and specify that the request be issued only when an element is 50% + visible.

+
+
+

The hx-trigger attribute certainly is the most complex in htmx. More details + and examples can be found in its documentation.

+
+
+
+
+

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:

+
+
+
+
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
+
+
<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
-
-
<div hx-target="body"  (1)
+                        
+
+
+

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
+
+
<div hx-target="body"  (1)
      hx-sync="this">  (2)
     <button hx-get="/contacts"> (1)
       Get Contacts
@@ -9868,173 +15017,210 @@ both buttons in a div and eliminate the redundant hx-target
-
-
-
-
    -
  1. -

    Hoist the duplicate hx-target attributes to the parent div

    -
  2. -
  3. -

    Synchronize on the parent div

    -
  4. -
-
-
-

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.

-
-
-

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 on the htmx website.

-
-
-
-
-

9.2. Events

-
-

We have been working with JavaScript events in htmx primarily via the hx-trigger attribute. This simple (well, not -so simple) attribute has proven to be a powerful mechanism for driving our application using a declarative, HTML-friendly -syntax.

-
-
-

Events turn out to be a crucial component of both the extension of HTML as a hypermedia, as well as a crucial component -of hypermedia-friendly scripting. Events are the “glue” that brings the DOM, HTML, htmx and scripting together, with the -DOM acting as a sophisticated "event bus" for our application. We really can’t stress how important Events are for -building an advanced Hypermedia-Driven Application, and we encourage you to learn them -in depth.

-
-
-

htmx-generated Events

-
-

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:

-
-
-
-
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

-
-
-
-
-
-

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: your 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
-
-
document.body.addEventListener("htmx:configRequest", function(configEvent){
+                        
+
+
+
    +
  1. +

    Hoist the duplicate hx-target attributes to the parent div +

    +
  2. +
  3. +

    Synchronize on the parent div

    +
  4. +
+
+
+

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.

+
+
+

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 on the htmx website.

+
+
+
+
+

9.2. Events

+
+

We have been working with JavaScript events in htmx primarily via the hx-trigger + attribute. This simple (well, not + so simple) attribute has proven to be a powerful mechanism for driving our application using a + declarative, HTML-friendly + syntax.

+
+
+

Events turn out to be a crucial component of both the extension of HTML as a hypermedia, as well + as a crucial component + of hypermedia-friendly scripting. Events are the “glue” that brings the DOM, HTML, + htmx and scripting together, with the + DOM acting as a sophisticated "event bus" for our application. We really can’t stress how + important Events are for + building an advanced Hypermedia-Driven Application, and we encourage you to learn them + in + depth. +

+
+
+

htmx-generated Events

+
+

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:

+
+
+
+
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 +

+
+
+
+
+
+

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: your 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
+
+
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

    -
  2. -
-
-
-

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 -on the htmx website.

-
-
-
-

Canceling a Request using htmx:abort

-
-

We can listen for any of the many useful events from htmx, and we can respond to those 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
-
-
<button id="contacts-btn" hx-get="/contacts" hx-target="body"> (1)
+                        
+
+
+
    +
  1. +

    retrieve the value from local storage and set it into a header

    +
  2. +
+
+
+

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 + on the htmx website. +

+
+
+
+

Canceling a Request using htmx:abort

+
+

We can listen for any of the many useful events from htmx, and we can respond to those 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
+
+
<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. -
  3. -

    JavaScript to look up the button and send it an htxm:abort event

    -
  4. -
-
-
-

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
-
-
<button id="contacts-btn" hx-get="/contacts" hx-target="body">
+                        
+
+
+
    +
  1. +

    A normal htmx-driven GET request to /contacts

    +
  2. +
  3. +

    JavaScript to look up the button and send it an htxm:abort event

    +
  4. +
+
+
+

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
+
+
<button id="contacts-btn" hx-get="/contacts" hx-target="body">
   Get Contacts
 </button>
 <button _="on click send htmx:abort to #contacts-btn
@@ -10042,51 +15228,68 @@ button unless an HTTP request is in flight, but that would be a pain to implemen
            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.

-
-
-

In addition to this request header, htmx also supports a response header also named HX-Trigger. 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, let’s 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 requests from the server side.

-
-
-

Here is what the client-side code might look like:

-
-
-
The Contacts Table
-
-
   <button hx-post="/integrations/1"> (1)
+                        
+
+
+

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.

+
+
+

In addition to this request header, htmx also supports a response header + also named HX-Trigger. 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, let’s 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 requests from the + server side.

+
+
+

Here is what the client-side code might look like:

+
+
+
The Contacts Table
+
+
   <button hx-post="/integrations/1"> (1)
      Pull Contacts From Integration
    </button>
 
@@ -10095,160 +15298,201 @@ attribute to listen for that event.  Now we can effectively trigger htmx request
     <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. -
  3. -

    This table listens for the event and refreshes when it occurs

    -
  4. -
-
-
-

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.

-
-
-

We are omitting the server-side implementation of this feature in the interest of simplicity, but this gives you -an idea of how the HX-Trigger response header can be used to coordinate sophisticated interactions in the DOM.

-
-
-
-
-

9.3. 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 are some of the more important headers you can use to change htmx behavior with -HTTP responses:

-
-
-
-
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 in the htmx documentation.

-
-
-

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
-
-
document.body.addEventListener('htmx:beforeSwap', function(evt) { (1)
+                        
+
+
+
    +
  1. +

    The response to this request may conditionally trigger the + contacts-updated event

    +
  2. +
  3. +

    This table listens for the event and refreshes when it occurs

    +
  4. +
+
+
+

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.

+
+
+

We are omitting the server-side implementation of this feature in the interest of simplicity, + but this gives you + an idea of how the HX-Trigger response header can be used to coordinate + sophisticated interactions in the DOM.

+
+
+
+
+

9.3. 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 are some of the more important headers you can use to + change htmx behavior with + HTTP responses:

+
+
+
+
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 in the htmx documentation.

+
+
+

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
+
+
document.body.addEventListener('htmx:beforeSwap', function(evt) { (1)
     if(evt.detail.xhr.status === 404){ (2)
         showNotFoundError();
     }
 });
-
-
-
-
    -
  1. -

    hook into the htmx:beforeSwap event

    -
  2. -
  3. -

    if the response code is a 404, show the user a dialog

    -
  4. -
-
-
-

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 at htmx.org.

-
-
-
-
-

9.4. 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, we 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
-
-
   <button hx-post="/integrations/1"> (1)
+                        
+
+
+
    +
  1. +

    hook into the htmx:beforeSwap event

    +
  2. +
  3. +

    if the response code is a 404, show the user a dialog

    +
  4. +
+
+
+

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 at htmx.org.

+
+
+
+
+

9.4. 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, we 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
+
+
   <button hx-post="/integrations/1"> (1)
      Pull Contacts From Integration
    </button>
 
@@ -10257,29 +15501,33 @@ a server-triggered event via the HX-Trigger response header.

<table id="contacts-table"> (2) ... </table>
-
-
-
-
    -
  1. -

    the button still issues a POST to /integrations/1

    -
  2. -
  3. -

    the table no longer listens for an event, but it now has an id

    -
  4. -
-
-
-

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, -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
-
-
HTTP/1.1 200 OK
+                        
+
+
+
    +
  1. +

    the button still issues a POST to /integrations/1

    +
  2. +
  3. +

    the table no longer listens for an event, but it now has an id

    +
  4. +
+
+
+

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, + 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
+
+
HTTP/1.1 200 OK
 Content-Type: text/html; charset=utf-8
 ...
 
@@ -10288,94 +15536,123 @@ 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. -
  3. -

    this content will be removed from the response and swapped by id

    -
  4. -
-
-
-

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.

-
-
-

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 too 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!

-
-
-
-
-

9.5. 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 use 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
-
-
htmx:configRequest
+                        
+
+
+
    +
  1. +

    this content will be placed in the button

    +
  2. +
  3. +

    this content will be removed from the response and swapped by id

    +
  4. +
+
+
+

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.

+
+
+

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 too 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!

+
+
+
+
+

9.5. 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 use 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
+
+
htmx:configRequest
 <a href="/docs/">
 Object { parameters: {}, unfilteredParameters: {}, headers: {…}, target: body, verb: "get", errors: [], withCredentials: false, timeout: 0, path: "/docs/", triggeringEvent: a
 , … }
@@ -10412,448 +15689,567 @@ Object { xhr: XMLHttpRequest, target: body, requestConfig: {…}, etc: {}, pathI
 htmx.js:439:29
 htmx:beforeSwap
 <body hx-ext="class-tools, preload">
-
-
-
-

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 occurring 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
-
-
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.

-
-
-
-
-

9.6. 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 the topic for more information.

-
-
-

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.

-
-
-
-
-

9.7. 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.

-
-
-

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
-
-
<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.

-
-
-
-
-
-

10. Client Side Scripting

-
-
-
-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. -
-
-— Roy Fielding
-Architectural Styles and the Design of Network-based Software Architectures -
-
-
-

Thus far we have (mostly) avoided writing any JavaScript (or _hyperscript) in Contact.app, mainly because the functionality -we implemented has not required it. In this chapter we are going to look at scripting and, in particular, hypermedia-friendly -scripting within the context of a Hypermedia-Driven Application.

-
-
-

10.1. Is Scripting Allowed?

-
-

A common criticism of the web is that it’s being misused. There is a narrative that WWW was created as a delivery system -for “documents”, and only came to be used for “applications” by way of an accident or bizarre circumstances.

-
-
-

However, the concept of hypermedia challenges the split of document and application, the distinction of code and data, of -operator and operand. Hypermedia systems like HyperCard, which preceded the web, featured rich capabilities for active -and interactive experiences, including scripting.

-
-
-

It is true in many ways that HTML, as specified and implemented, lacks affordances needed to build the kinds of applications -that were implemented within those older systems. None of this means, however, that hypermedia’s purpose is “documents” -over “applications”.

-
-
-

Rather, while the theoretical foundation is there, the implementation is underdeveloped. With JavaScript being the -only extension point and hypermedia controls not being well integrated to JavaScript (why can’t one click a link without -halting the program?), developers have not internalized hypermedia and have instead used the Web as a dumb pipe for apps -that imitate “native” ones.

-
-
-

A goal of this book is to show that it is possible to build sophisticated web applications using the original technology -of the web, hypermedia, without the application developer needing to reach for the abstractions provided by the large, -popular JavaScript frameworks.

-
-
-

htmx itself is, of course, written in JavaScript, and one of its advantages is that hypermedia interactions that go -through htmx expose a rich interface to JavaScript code with configuration, events, and htmx’s own extension support.

-
-
-

Because htmx expands the expressiveness of HTML so much it removes the need for scripting in many situations. -This makes htmx attractive to people who don’t want to write JavaScript, and there are many of those sorts of developers, -sick to death of the complexity of Single Page Application frameworks.

-
-
-

However, dunking on JavaScript is not the aim of the htmx project.

-
-
-

The goal of htmx is not less JavaScript, per se, but rather less code.

-
-
-

Scripting has been a massive force multiplier for the web. Using scripting, web application developers are not only able -to enhance their HTML websites, but also create fully-fledged client-side applications that can often compete with -native, thick client applications.

-
-
-

This JavaScript-centric approach to building web applications is a testament to the power of the web and to the sophistication -of web browsers in particular. It has its place in web development: there are situations where the hypermedia approach -simply can’t provide the level of interaction that an SPA can.

-
-
-

However, in addition to this more JavaScript-centric style, we want to develop a style of scripting more compatible and -consistent with Hypermedia-Driven Applications.

-
-
-
-

10.2. Scripting for Hypermedia

-
-

Borrowing from Roy Fielding’s notion of “constraints” defining REST, we offer two constraints of hypermedia-friendly -scripting. You are scripting in an HDA-compatible manner if the following two constraints are adhered to:

-
-
-
    -
  • -

    The main data format exchanged between server and client must be hypermedia, the same as it would be without scripting.

    -
  • -
  • -

    Client-side state, outside the DOM itself, is kept to a minimum.

    -
  • -
-
-
-

The goal of these constraints is to confine scripting to where it shines best and where nothing else comes close: -interaction design. Business logic and presentation logic are the responsibility of the server, where we can pick -whichever languages or tools are appropriate for our business domain.

-
- -
-

Satisfying these two constraints sometimes requires us to diverge from what is typically considered best practice for -JavaScript. Keep in mind that the cultural wisdom of JavaScript was largely developed in JavaScript-centric SPA applications.

-
-
-

The Hypermedia-driven Application cannot as comfortably fall back on this tradition. This chapter is our contribution to the -development of a new style and best practices for what we are calling Hypermedia-Driven Applications.

-
-
-

Unfortunately, simply listing “best practices” is rarely convincing or edifying. To be honest, it’s boring.

-
-
-

Instead, we will demonstrate these best practices by implementing client-side features in Contact.app. We will -implement three different features, each showing different aspects of hypermedia-friendly scripting:

-
-
-
    -
  • -

    We will add an overflow menu to hold the Edit, View and Delete actions, to clean up visual clutter in our list of contacts

    -
  • -
  • -

    We will add an improved interface for bulk deletion

    -
  • -
  • -

    We will add a keyboard shortcut for focusing the search box

    -
  • -
-
-
-

The important takeaway in the implementation of each of these features is that, while they are implemented entirely in -the client-side using scripting, they don’t exchange information with the server via a non-hypermedia format, such -as JSON, and that they don’t store a significant amount of state outside of the DOM itself.

-
-
-
-

10.3. Scripting tools for the Web

-
-

The primary scripting language for the web is, of course, JavaScript, which is ubiquitous in web development today.

-
-
-

A bit of interesting internet lore, however, is that JavaScript was not always the only built-in option. -As the quote from Roy Fielding at the start of this chapter hints, “applets” written in other languages such as Java were considered to be -part of the scripting infrastructure of the web. In addition, there was a time period when Internet Explorer supported VBScript, -a scripting language based on Visual Basic.

-
-
-

Today, we have a variety of transcompilers (often shortened to transpilers) that convert many languages to JavaScript, -such as TypeScript, Dart, Kotlin, ClojureScript, F# and more. There is also the WebAssembly (WASM) bytecode format, which -is supported as a compilation target for C, Rust, and the WASM-first language AssemblyScript.

-
-
-

However, most of these options are not geared towards a hypermedia-friendly style of scripting. Compile-to-JS languages -are often paired with SPA-oriented libraries (Dart and AngularDart, ClojureScript and Reagent, F# and Elm), and WASM is -currently mainly geared toward linking to C/C++ libraries from JavaScript.

-
-
-

We will instead focus on three client-side scripting technologies that are hypermedia-friendly:

-
-
-
    -
  • -

    VanillaJS, that is, using JavaScript without depending on any framework.

    -
  • -
  • -

    Alpine.js, a JavaScript library for adding behavior directly in HTML.

    -
  • -
  • -

    _hyperscript, a non-JavaScript scripting language created alongside htmx. Like AlpineJS, _hyperscript is usually embedded in HTML.

    -
  • -
-
-
-

Let’s take a quick look at each of these scripting options, so we know what we are dealing with.

-
-
-

Note that, as with CSS, we are not going to do a deep dive into any of these options. Instead, we are going to show you -just enough to give you a flavor of how they work and, we hope, spark your interest in looking into any of them more extensively.

-
-
-
-

10.4. Vanilla JavaScript

-
-
-No code is faster than no code. -
-
-— Merb -
-
-
-

Vanilla JavaScript is simply using plain JavaScript in your application, without any intermediate layers. -The term “Vanilla” entered frontend web dev parlance as it became assumed that any sufficiently “advanced” web app would -use some library with a name ending in “.js”. As JavaScript matured as a scripting language, however, standardized across browsers and -provided more and more functionality, these frameworks and libraries became less important.

-
-
-

Somewhat ironically though, as JavaScript became more powerful and removed the need for the first generation of -JavaScript libraries such as jQuery, it also enabled people to build complex SPA libraries. These SPA libraries are often -even more elaborate than the original first generation of JavaScript libraries.

-
-
-

A quote from the website http://vanilla-js.com, which is well worth visiting even though it’s slightly out of date, -captures the situation well:

-
-
-
-VanillaJS is the lowest-overhead, most comprehensive framework I’ve ever used. -
-
-— http://vanilla-js.com -
-
-
-

With JavaScript having matured as a scripting language, this is certainly the case for many applications. It is -especially true in the case of HDAs, since, by using hypermedia, your application will not need many of the features -typically provided by more elaborate Single Page Application JavaScript frameworks:

-
-
-
    -
  • -

    Client-side routing

    -
  • -
  • -

    An abstraction over DOM manipulation, i.e.: templates that automatically update when referenced variables change

    -
  • -
  • -

    Server side rendering Rendering here refers to HTML generation. Framework support for server-side rendering is not needed in a HDA because generating HTML on the server is the default.

    -
  • -
  • -

    Attaching dynamic behavior to server-rendered tags on load, i.e. “hydration”

    -
  • -
  • -

    Network requests

    -
  • -
-
-
-

Without all this complexity being handled in JavaScript, your framework needs are dramatically reduced.

-
-
-

One of the best things about VanillaJS is how you install it: you don’t have to!

-
-
-

You can just start writing JavaScript in your web application, and it will simply work.

-
-
-

That’s the good news. The bad news is that, despite improvements over the last decade, JavaScript has some significant -limitations as a scripting language that can make it a less than ideal as a stand-alone scripting technology for -Hypermedia Driven Applications:

-
-
-
    -
  • -

    Being as established as it is, it has accreted a lot of features and warts.

    -
  • -
  • -

    It has a complicated and confusing set of features for working with asynchronous code.

    -
  • -
  • -

    Working with events is surprisingly difficult.

    -
  • -
  • -

    DOM APIs (a large portion of which were originally designed for Java, yes Java) -are verbose and don’t have a habit of making common functionality easy to use.

    -
  • -
-
-
-

None of these limitations are deal-breakers, of course. Many of them are gradually being fixed and many people prefer -the “close to the metal” (for lack of a better term) nature of vanilla JavaScript over more elaborate client-side scripting approaches.

-
-
-

A Simple Counter

-
-

To dive into vanilla JavaScript as a front end scripting option, let’s create a simple counter widget.

-
-
-

Counter widgets are a common “Hello World” example for JavaScript frameworks, so looking at how it can be done in -vanilla JavaScript (as well as the other options we are going to look at) will be instructive.

-
-
-

Our counter widget will be very simple: it will have a number, shown as text, and a button that increments the number.

-
-
-

One problem with tackling this problem in vanilla JavaScript is that it lacks one thing that most JavaScript frameworks -provide: a default code and architectural style.

-
-
-

With vanilla JavaScript, there are no rules!

-
-
-

This isn’t all bad. It presents a great opportunity to take a small journey through various styles that people have -developed for writing their JavaScript.

-
-
-
An Inline Implementation
-
-

To begin, let’s start with the simplest thing imaginable: all of our JavaScript will be written inline, directly in the -HTML. When the button is clicked, we will look up the output element holding the number, and increment the number -contained within it.

-
-
-
Counter in vanilla JavaScript, inline version
-
-
<section class="counter">
+                        
+
+
+

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 occurring 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
+
+
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.

+
+
+
+
+

9.6. 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 the topic for more information.

+
+
+

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.

+
+
+
+
+

9.7. 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.

+
+
+

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
+
+
<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.

+
+
+
+
+
+

10. Client Side Scripting

+
+
+
+ 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. +
+
+ — Roy Fielding
+ Architectural Styles and the Design of Network-based Software Architectures +
+
+
+

Thus far we have (mostly) avoided writing any JavaScript (or _hyperscript) in Contact.app, mainly + because the functionality + we implemented has not required it. In this chapter we are going to look at scripting and, in + particular, hypermedia-friendly + scripting within the context of a Hypermedia-Driven Application.

+
+
+

10.1. Is Scripting Allowed?

+
+

A common criticism of the web is that it’s being misused. There is a narrative that WWW was + created as a delivery system + for “documents”, and only came to be used for “applications” by way of + an accident or bizarre circumstances.

+
+
+

However, the concept of hypermedia challenges the split of document and application, the + distinction of code and data, of + operator and operand. Hypermedia systems like HyperCard, which preceded the web, featured rich + capabilities for active + and interactive experiences, including scripting.

+
+
+

It is true in many ways that HTML, as specified and implemented, lacks affordances needed to + build the kinds of applications + that were implemented within those older systems. None of this means, however, that + hypermedia’s purpose is “documents” + over “applications”.

+
+
+

Rather, while the theoretical foundation is there, the implementation is underdeveloped. With + JavaScript being the + only extension point and hypermedia controls not being well integrated to JavaScript (why + can’t one click a link without + halting the program?), developers have not internalized hypermedia and have instead used the Web + as a dumb pipe for apps + that imitate “native” ones.

+
+
+

A goal of this book is to show that it is possible to build sophisticated web applications using + the original technology + of the web, hypermedia, without the application developer needing to reach for the abstractions + provided by the large, + popular JavaScript frameworks.

+
+
+

htmx itself is, of course, written in JavaScript, and one of its advantages is that hypermedia + interactions that go + through htmx expose a rich interface to JavaScript code with configuration, events, and + htmx’s own extension support.

+
+
+

Because htmx expands the expressiveness of HTML so much it removes the need for scripting in many + situations. + This makes htmx attractive to people who don’t want to write JavaScript, and there are + many of those sorts of developers, + sick to death of the complexity of Single Page Application frameworks.

+
+
+

However, dunking on JavaScript is not the aim of the htmx project.

+
+
+

The goal of htmx is not less JavaScript, per se, but rather less code.

+
+
+

Scripting has been a massive force multiplier for the web. Using scripting, web application + developers are not only able + to enhance their HTML websites, but also create fully-fledged client-side applications that can + often compete with + native, thick client applications.

+
+
+

This JavaScript-centric approach to building web applications is a testament to the power of the + web and to the sophistication + of web browsers in particular. It has its place in web development: there are situations where + the hypermedia approach + simply can’t provide the level of interaction that an SPA can.

+
+
+

However, in addition to this more JavaScript-centric style, we want to develop a style of + scripting more compatible and + consistent with Hypermedia-Driven Applications.

+
+
+
+

10.2. Scripting for Hypermedia

+
+

Borrowing from Roy Fielding’s notion of “constraints” defining REST, we offer + two constraints of hypermedia-friendly + scripting. You are scripting in an HDA-compatible manner if the following two constraints are + adhered to:

+
+
+
    +
  • +

    The main data format exchanged between server and client must be hypermedia, the same as + it would be without scripting.

    +
  • +
  • +

    Client-side state, outside the DOM itself, is kept to a minimum.

    +
  • +
+
+
+

The goal of these constraints is to confine scripting to where it shines best and where nothing + else comes close: + interaction design. Business logic and presentation logic are the responsibility of the + server, where we can pick + whichever languages or tools are appropriate for our business domain. +

+
+ +
+

Satisfying these two constraints sometimes requires us to diverge from what is typically + considered best practice for + JavaScript. Keep in mind that the cultural wisdom of JavaScript was largely developed in + JavaScript-centric SPA applications.

+
+
+

The Hypermedia-driven Application cannot as comfortably fall back on this tradition. This chapter + is our contribution to the + development of a new style and best practices for what we are calling Hypermedia-Driven + Applications.

+
+
+

Unfortunately, simply listing “best practices” is rarely convincing or edifying. To + be honest, it’s boring.

+
+
+

Instead, we will demonstrate these best practices by implementing client-side features in + Contact.app. We will + implement three different features, each showing different aspects of hypermedia-friendly + scripting:

+
+
+
    +
  • +

    We will add an overflow menu to hold the Edit, View and Delete + actions, to clean up visual clutter in our list of contacts

    +
  • +
  • +

    We will add an improved interface for bulk deletion

    +
  • +
  • +

    We will add a keyboard shortcut for focusing the search box

    +
  • +
+
+
+

The important takeaway in the implementation of each of these features is that, while they are + implemented entirely in + the client-side using scripting, they don’t exchange information with the server + via a non-hypermedia format, such + as JSON, and that they don’t store a significant amount of state outside of the DOM + itself.

+
+
+
+

10.3. Scripting tools for the Web

+
+

The primary scripting language for the web is, of course, JavaScript, which is ubiquitous in web + development today.

+
+
+

A bit of interesting internet lore, however, is that JavaScript was not always the only built-in + option. + As the quote from Roy Fielding at the start of this chapter hints, “applets” written + in other languages such as Java were considered to be + part of the scripting infrastructure of the web. In addition, there was a time period when + Internet Explorer supported VBScript, + a scripting language based on Visual Basic.

+
+
+

Today, we have a variety of transcompilers (often shortened to transpilers) + that convert many languages to JavaScript, + such as TypeScript, Dart, Kotlin, ClojureScript, F# and more. There is also the WebAssembly + (WASM) bytecode format, which + is supported as a compilation target for C, Rust, and the WASM-first language AssemblyScript. +

+
+
+

However, most of these options are not geared towards a hypermedia-friendly style of scripting. + Compile-to-JS languages + are often paired with SPA-oriented libraries (Dart and AngularDart, ClojureScript and Reagent, + F# and Elm), and WASM is + currently mainly geared toward linking to C/C++ libraries from JavaScript.

+
+
+

We will instead focus on three client-side scripting technologies that are + hypermedia-friendly:

+
+
+
    +
  • +

    VanillaJS, that is, using JavaScript without depending on any framework.

    +
  • +
  • +

    Alpine.js, a JavaScript library for adding behavior directly in HTML.

    +
  • +
  • +

    _hyperscript, a non-JavaScript scripting language created alongside htmx. Like AlpineJS, + _hyperscript is usually embedded in HTML.

    +
  • +
+
+
+

Let’s take a quick look at each of these scripting options, so we know what we are dealing + with.

+
+
+

Note that, as with CSS, we are not going to do a deep dive into any of these options. Instead, we + are going to show you + just enough to give you a flavor of how they work and, we hope, spark your interest in looking + into any of them more extensively.

+
+
+
+

10.4. Vanilla JavaScript

+
+
+ No code is faster than no code. +
+
+ — Merb +
+
+
+

Vanilla JavaScript is simply using plain JavaScript in your application, without any intermediate + layers. + The term “Vanilla” entered frontend web dev parlance as it became assumed that any + sufficiently “advanced” web app would + use some library with a name ending in “.js”. As JavaScript matured as a scripting + language, however, standardized across browsers and + provided more and more functionality, these frameworks and libraries became less important.

+
+
+

Somewhat ironically though, as JavaScript became more powerful and removed the need for the first + generation of + JavaScript libraries such as jQuery, it also enabled people to build complex SPA libraries. + These SPA libraries are often + even more elaborate than the original first generation of JavaScript libraries.

+
+
+

A quote from the website http://vanilla-js.com, + which is well worth visiting even though it’s slightly out of date, + captures the situation well:

+
+
+
+ VanillaJS is the lowest-overhead, most comprehensive framework I’ve ever used. +
+
+ — http://vanilla-js.com +
+
+
+

With JavaScript having matured as a scripting language, this is certainly the case for many + applications. It is + especially true in the case of HDAs, since, by using hypermedia, your application will not need + many of the features + typically provided by more elaborate Single Page Application JavaScript frameworks:

+
+
+
    +
  • +

    Client-side routing

    +
  • +
  • +

    An abstraction over DOM manipulation, i.e.: templates that automatically update when + referenced variables change

    +
  • +
  • +

    Server side rendering Rendering here refers to + HTML generation. Framework support for server-side rendering is not needed in a HDA + because generating HTML on the server is the default.

    +
  • +
  • +

    Attaching dynamic behavior to server-rendered tags on load, i.e. “hydration” +

    +
  • +
  • +

    Network requests

    +
  • +
+
+
+

Without all this complexity being handled in JavaScript, your framework needs are dramatically + reduced.

+
+
+

One of the best things about VanillaJS is how you install it: you don’t have to!

+
+
+

You can just start writing JavaScript in your web application, and it will simply work.

+
+
+

That’s the good news. The bad news is that, despite improvements over the last decade, + JavaScript has some significant + limitations as a scripting language that can make it a less than ideal as a stand-alone + scripting technology for + Hypermedia Driven Applications:

+
+
+
    +
  • +

    Being as established as it is, it has accreted a lot of features and warts.

    +
  • +
  • +

    It has a complicated and confusing set of features for working with asynchronous code. +

    +
  • +
  • +

    Working with events is surprisingly difficult.

    +
  • +
  • +

    DOM APIs (a large portion of which were originally designed for Java, yes Java) + are verbose and don’t have a habit of making common functionality easy to use.

    +
  • +
+
+
+

None of these limitations are deal-breakers, of course. Many of them are gradually being fixed + and many people prefer + the “close to the metal” (for lack of a better term) nature of vanilla JavaScript + over more elaborate client-side scripting approaches.

+
+
+

A Simple Counter

+
+

To dive into vanilla JavaScript as a front end scripting option, let’s create a simple + counter widget.

+
+
+

Counter widgets are a common “Hello World” example for JavaScript frameworks, so + looking at how it can be done in + vanilla JavaScript (as well as the other options we are going to look at) will be + instructive.

+
+
+

Our counter widget will be very simple: it will have a number, shown as text, and a button + that increments the number.

+
+
+

One problem with tackling this problem in vanilla JavaScript is that it lacks one thing that + most JavaScript frameworks + provide: a default code and architectural style.

+
+
+

With vanilla JavaScript, there are no rules!

+
+
+

This isn’t all bad. It presents a great opportunity to take a small journey through + various styles that people have + developed for writing their JavaScript.

+
+
+
An Inline Implementation
+
+

To begin, let’s start with the simplest thing imaginable: all of our JavaScript + will be written inline, directly in the + HTML. When the button is clicked, we will look up the output element + holding the number, and increment the number + contained within it.

+
+
+
Counter in vanilla JavaScript, inline version
+
+
<section class="counter">
   <output id="my-output">0</output> (1)
   <button
     onclick=" (2)
@@ -10862,335 +16258,414 @@ contained within it.

" >Increment</button> </section>
-
-
-
-
    -
  1. -

    Our output element has an ID to help us find it

    -
  2. -
  3. -

    We use the onclick attribute to add an event listener

    -
  4. -
  5. -

    Find the output via a querySelector() call

    -
  6. -
  7. -

    JavaScript allows us use the ++ operator on strings

    -
  8. -
-
-
-

Not too bad.

-
-
-

It’s not the most beautiful code, and can be irritating especially if you aren’t used to the DOM APIs.

-
-
-

It’s a little annoying that we needed to add an id to the output element. The document.querySelector() function -is a bit verbose compared with, say, the $ function, as provided by jQuery.

-
-
-

But it works. It’s also easy enough to understand, and crucially it doesn’t require any other JavaScript libraries.

-
-
-

So that’s the simple, inline approach with VanillaJS.

-
-
-
-
Separating Our Scripting Out
-
-

While the inline implementation is simple in some sense, a more standard way to write this code would be to move the code -into a separate JavaScript file. This JavaScript file would then either be linked to via a <script src> tag or -placed into an inline <script> tag by a build process.

-
-
-

Here we see the HTML and JavaScript separated out from one another, in different files. The HTML is now “cleaner” in -that there is no JavaScript in it.

-
-
-

The JavaScript is a bit more complex than in our inline version: we need to look up the button using a query selector -and add an event listener to handle the click event and increment the counter.

-
-
-
Counter HTML
-
-
<section class="counter">
+                            
+
+
+
    +
  1. +

    Our output element has an ID to help us find it

    +
  2. +
  3. +

    We use the onclick attribute to add an event listener

    +
  4. +
  5. +

    Find the output via a querySelector() call

    +
  6. +
  7. +

    JavaScript allows us use the ++ operator on strings

    +
  8. +
+
+
+

Not too bad.

+
+
+

It’s not the most beautiful code, and can be irritating especially if you + aren’t used to the DOM APIs.

+
+
+

It’s a little annoying that we needed to add an id to the + output element. The document.querySelector() function + is a bit verbose compared with, say, the $ function, as provided by jQuery. +

+
+
+

But it works. It’s also easy enough to understand, and crucially it doesn’t + require any other JavaScript libraries.

+
+
+

So that’s the simple, inline approach with VanillaJS.

+
+
+
+
Separating Our Scripting Out
+
+

While the inline implementation is simple in some sense, a more standard way to write + this code would be to move the code + into a separate JavaScript file. This JavaScript file would then either be linked to via + a <script src> tag or + placed into an inline <script> tag by a build process.

+
+
+

Here we see the HTML and JavaScript separated out from one another, in different + files. The HTML is now “cleaner” in + that there is no JavaScript in it.

+
+
+

The JavaScript is a bit more complex than in our inline version: we need to look up the + button using a query selector + and add an event listener to handle the click event and increment the counter. +

+
+
+
Counter HTML
+
+
<section class="counter">
   <output id="my-output">0</output>
   <button class="increment-btn">Increment</button>
 </section>
-
-
-
-
Counter JavaScript
-
-
const counterOutput = document.querySelector("#my-output") (1)
+                            
+
+
+
Counter JavaScript
+
+
const counterOutput = document.querySelector("#my-output") (1)
 const incrementBtn  = document.querySelector(".counter .increment-btn") (2)
 
 incrementBtn.addEventListener("click", e => { (3)
   counterOutput.innerHTML++ (4)
 })
-
-
-
-
    -
  1. -

    Find the output element

    -
  2. -
  3. -

    and the button

    -
  4. -
  5. -

    We use addEventListener, which is preferable to onclick for many reasons

    -
  6. -
  7. -

    The logic stays the same, only the structure around it changes

    -
  8. -
-
-
-

In moving the JavaScript out to another file, we are following a software design principle known as Separation of Concerns (SoC).

-
-
-

Separation of Concerns posits that the various “concerns” (or aspects) of a software project should be divided up into -multiple files, so that they don’t “pollute” one another. JavaScript isn’t markup, so it shouldn’t be in your HTML, -it should be elsewhere. Styling information, similarly, isn’t markup, and so it belongs in a separate file as well -(A CSS file, for example.)

-
-
-

For quite some time, this Separation of Concerns was considered the “orthodox” way to build web applications.

-
-
-

A stated goal of Separation of Concerns is that we should be able to modify and evolve each concern independently, with -confidence that we won’t break any of the other concerns.

-
-
-

However, let’s look at exactly how this principle has worked out in our simple counter example. If you look closely -at the new HTML, it turns out that we’ve had to add a class to the button. We added this class so that we could look the button -up in JavaScript and add in an event handler for the “click” event.

-
-
-

Now, in both the HTML and the JavaScript, this class name is just a string and there isn’t any process to verify that -the button has the right classes on it or its parents to ensure that the event handler is actually added to the right element.

-
-
-

Unfortunately, it has turned out that the careless use of CSS selectors in JavaScript can cause what is known as -jQuery soup. jQuery soup is a situation where:

-
-
-
    -
  • -

    The JavaScript that attaches a given behavior to a given element is difficult to find.

    -
  • -
  • -

    Code reuse is difficult.

    -
  • -
  • -

    The code ends up wildly disorganized and “flat”, with lots of unrelated event handlers mixed together.

    -
  • -
-
-
-

The name “jQuery soup” comes from the fact that early JavaScript-heavy applications were typically built in jQuery, -which, perhaps inadvertently, tended to encourage this style of JavaScript.

-
-
-

So, you can see that the notion of Separation of Concerns doesn’t always work out as well as promised: our concerns -end up intertwined or coupled pretty deeply, even when we separate them into different files.

-
-
-

To show that it isn’t just naming between concerns that can get you into trouble, consider another small change to our HTML -that demonstrates the problems with our separation of concerns: imagine that we decide to change the number field from -an <output> tag to an <input type="number">.

-
-
-

This small change to our HTML will break our JavaScript, despite the fact we have “separated” our concerns.

-
-
-

The fix for this issue is simple enough (we would need to change the .textContent property to .value property), but -it demonstrates the burden of synchronizing markup changes and code changes across multiple files. Keeping everything -in sync can become increasingly difficult as your application size increases.

-
-
-

The fact that small changes to our HTML can break our scripting indicates that the two are tightly coupled, despite being -broken up into multiple files. This tight coupling suggests that separation between HTML and JavaScript (and CSS) is often -an illusory separation of concerns: the concerns are sufficiently related to one another that they aren’t easily separated.

-
-
-

In Contact.app we are not concerned with “structure”, “styling” or “behavior”; we are concerned with collecting contact -info and presenting it to users. SoC, in the way it’s formulated in web development orthodoxy, is not really an inviolate -architectural guideline, but rather a stylistic choice that, as we can see, can even become a hindrance.

-
-
-
-
Locality
-
-

It turns out that there is a burgeoning reaction against the Separation of Concerns design principle. Consider the -following web technologies and techniques:

-
-
-
    -
  • -

    JSX

    -
  • -
  • -

    LitHTML

    -
  • -
  • -

    CSS-in-JS

    -
  • -
  • -

    Single-File Components

    -
  • -
  • -

    Filesystem based routing

    -
  • -
-
-
-

Each of these technologies colocate code in various languages that address a single feature (typically a UI widget).

-
-
-

All of them mix implementation concerns together in order to present a unified abstraction to the end-user. Separating -technical detail concerns just isn’t as much of an, ahem, concern.

-
- -
-

In a Hypermedia Driven Application, we feel that the Locality of Behaviour design principle is often more important than -the more traditional Separation of Concerns design principle.

-
-
-
-
What To Do With Our Counter?
-
-

So, should we go back to the onclick attribute way of doing things? That approach certainly wins in Locality of -Behavior, and has the additional benefit that it is baked into HTML.

-
-
-

Unfortunately, however, the on* JavaScript attributes also come with some drawbacks:

-
-
-
    -
  • -

    They don’t support custom events.

    -
  • -
  • -

    There is no good mechanism for associating long-lasting variables with an element — all variables are discarded when an event listener completes executing.

    -
  • -
  • -

    If you have multiple instances of an element, you will need to repeat the listener code on each, or use something more clever like event delegation.

    -
  • -
  • -

    JavaScript code that directly manipulates the DOM gets verbose, and clutters the markup.

    -
  • -
  • -

    An element cannot listen for events on another element.

    -
  • -
-
-
-

Consider this common situation: you have a popup, and you want it to be dismissed when a user clicks outside of it. The -listener will need to be on the body element in this situation, far away from the actual popup markup. This means that -the body element would need to have listeners attached to it that deal with many unrelated components. Some of these -components may not even be on the page when it was first rendered, if they are added dynamically after the initial -HTML page is rendered.

-
-
-

So vanilla JavaScript and Locality of Behaviour don’t seem to mesh quite as well as we would like them to.

-
-
-

The situation is not hopeless, however: it’s important to understand that LoB does not require behavior to be implemented -at a use site, but merely invoked there. That is, we don’t need to write all our code on a given element, we just -need to make it clear that a given element is invoking some code, which can be located elsewhere.

-
-
-

Keeping this in mind, it is possible to improve LoB while writing JavaScript in a separate file, provided we have a -reasonable system for structuring our JavaScript.

-
-
-
-
-

RSJS

-
-

RSJS (the “Reasonable System for JavaScript Structure”, https://ricostacruz.com/rsjs/) is a set of guidelines for -JavaScript architecture targeted at “a typical non-SPA website”. RSJS provides a solution to the lack of a standard code -style for vanilla JavaScript that we mentioned earlier.

-
-
-

We won’t reproduce all the RSJS guidelines here, but here are the ones most relevant for our counter widget:

-
-
-
    -
  • -

    “Use data- attributes” in HTML - invoking behavior via adding data attributes makes it obvious there is JavaScript -happening, as opposed to using random classes or IDs that may be mistakenly removed or changed

    -
  • -
  • -

    “One component per file” - the name of the file should match the data attribute so that it can be found easily, a win for LoB

    -
  • -
-
-
-

To follow the RSJS guidelines, let’s restructure our current HTML and JavaScript files. First, we will use data attributes, -that is, HTML attributes that begin with data-, a standard feature of HTML, to indicate that our HTML is a counter -component. We will then update our JavaScript to use an attribute selector that looks for the data-counter attribute -as the root element in our counter component and wires in the appropriate event handlers and logic. Additionally, let’s -rework the code to use querySelectorAll() and add the counter functionality to all counter components found on the -page. (You never know how many counter’s you might want!)

-
-
-

Here is what our code looks like now:

-
-
-
Counter in vanilla JavaScript, with RSJS
-
-
-
-
<section class="counter" data-counter> (1)
+                            
+
+
+
    +
  1. +

    Find the output element

    +
  2. +
  3. +

    and the button

    +
  4. +
  5. +

    We use addEventListener, which is preferable to onclick + for many reasons

    +
  6. +
  7. +

    The logic stays the same, only the structure around it changes

    +
  8. +
+
+
+

In moving the JavaScript out to another file, we are following a software design + principle known as Separation of Concerns (SoC).

+
+
+

Separation of Concerns posits that the various “concerns” (or aspects) of a + software project should be divided up into + multiple files, so that they don’t “pollute” one another. JavaScript + isn’t markup, so it shouldn’t be in your HTML, + it should be elsewhere. Styling information, similarly, isn’t markup, and + so it belongs in a separate file as well + (A CSS file, for example.)

+
+
+

For quite some time, this Separation of Concerns was considered the + “orthodox” way to build web applications.

+
+
+

A stated goal of Separation of Concerns is that we should be able to modify and evolve + each concern independently, with + confidence that we won’t break any of the other concerns.

+
+
+

However, let’s look at exactly how this principle has worked out in our simple + counter example. If you look closely + at the new HTML, it turns out that we’ve had to add a class to the button. We + added this class so that we could look the button + up in JavaScript and add in an event handler for the “click” event.

+
+
+

Now, in both the HTML and the JavaScript, this class name is just a string and there + isn’t any process to verify that + the button has the right classes on it or its parents to ensure that the event handler + is actually added to the right element.

+
+
+

Unfortunately, it has turned out that the careless use of CSS selectors in JavaScript can + cause what is known as + jQuery soup. jQuery soup is a situation where: +

+
+
+
    +
  • +

    The JavaScript that attaches a given behavior to a given element is difficult to + find.

    +
  • +
  • +

    Code reuse is difficult.

    +
  • +
  • +

    The code ends up wildly disorganized and “flat”, with lots of + unrelated event handlers mixed together.

    +
  • +
+
+
+

The name “jQuery soup” comes from the fact that early JavaScript-heavy + applications were typically built in jQuery, + which, perhaps inadvertently, tended to encourage this style of JavaScript.

+
+
+

So, you can see that the notion of Separation of Concerns doesn’t always work out + as well as promised: our concerns + end up intertwined or coupled pretty deeply, even when we separate them into different + files.

+
+
+

To show that it isn’t just naming between concerns that can get you into trouble, + consider another small change to our HTML + that demonstrates the problems with our separation of concerns: imagine that we decide + to change the number field from + an <output> tag to an <input type="number">.

+
+
+

This small change to our HTML will break our JavaScript, despite the fact we have + “separated” our concerns.

+
+
+

The fix for this issue is simple enough (we would need to change the + .textContent property to .value property), but + it demonstrates the burden of synchronizing markup changes and code changes across + multiple files. Keeping everything + in sync can become increasingly difficult as your application size increases.

+
+
+

The fact that small changes to our HTML can break our scripting indicates that the two + are tightly coupled, despite being + broken up into multiple files. This tight coupling suggests that separation between HTML + and JavaScript (and CSS) is often + an illusory separation of concerns: the concerns are sufficiently related to one another + that they aren’t easily separated.

+
+
+

In Contact.app we are not concerned with “structure”, + “styling” or “behavior”; we are concerned with collecting + contact + info and presenting it to users. SoC, in the way it’s formulated in web + development orthodoxy, is not really an inviolate + architectural guideline, but rather a stylistic choice that, as we can see, can even + become a hindrance.

+
+
+
+
Locality
+
+

It turns out that there is a burgeoning reaction against the Separation of + Concerns design principle. Consider the + following web technologies and techniques:

+
+
+
    +
  • +

    JSX

    +
  • +
  • +

    LitHTML

    +
  • +
  • +

    CSS-in-JS

    +
  • +
  • +

    Single-File Components

    +
  • +
  • +

    Filesystem based routing

    +
  • +
+
+
+

Each of these technologies colocate code in various languages that address a + single feature (typically a UI widget).

+
+
+

All of them mix implementation concerns together in order to present a unified + abstraction to the end-user. Separating + technical detail concerns just isn’t as much of an, ahem, concern.

+
+ +
+

In a Hypermedia Driven Application, we feel that the Locality of Behaviour design + principle is often more important than + the more traditional Separation of Concerns design principle.

+
+
+
+
What To Do With Our Counter?
+
+

So, should we go back to the onclick attribute way of doing things? That + approach certainly wins in Locality of + Behavior, and has the additional benefit that it is baked into HTML.

+
+
+

Unfortunately, however, the on* JavaScript attributes also come with some + drawbacks:

+
+
+
    +
  • +

    They don’t support custom events.

    +
  • +
  • +

    There is no good mechanism for associating long-lasting variables with an + element — all variables are discarded when an event listener + completes executing.

    +
  • +
  • +

    If you have multiple instances of an element, you will need to repeat the + listener code on each, or use something more clever like event delegation.

    +
  • +
  • +

    JavaScript code that directly manipulates the DOM gets verbose, and clutters the + markup.

    +
  • +
  • +

    An element cannot listen for events on another element.

    +
  • +
+
+
+

Consider this common situation: you have a popup, and you want it to be dismissed when a + user clicks outside of it. The + listener will need to be on the body element in this situation, far away from the actual + popup markup. This means that + the body element would need to have listeners attached to it that deal with many + unrelated components. Some of these + components may not even be on the page when it was first rendered, if they are added + dynamically after the initial + HTML page is rendered.

+
+
+

So vanilla JavaScript and Locality of Behaviour don’t seem to mesh quite + as well as we would like them to.

+
+
+

The situation is not hopeless, however: it’s important to understand that LoB does + not require behavior to be implemented + at a use site, but merely invoked there. That is, we don’t need to write + all our code on a given element, we just + need to make it clear that a given element is invoking some code, which can be + located elsewhere.

+
+
+

Keeping this in mind, it is possible to improve LoB while writing JavaScript in + a separate file, provided we have a + reasonable system for structuring our JavaScript.

+
+
+
+
+

RSJS

+
+

RSJS (the “Reasonable System for JavaScript Structure”, https://ricostacruz.com/rsjs/) is + a set of guidelines for + JavaScript architecture targeted at “a typical non-SPA website”. RSJS provides a + solution to the lack of a standard code + style for vanilla JavaScript that we mentioned earlier.

+
+
+

We won’t reproduce all the RSJS guidelines here, but here are the ones most relevant + for our counter widget:

+
+
+
    +
  • +

    “Use data- attributes” in HTML - invoking behavior via + adding data attributes makes it obvious there is JavaScript + happening, as opposed to using random classes or IDs that may be mistakenly removed + or changed

    +
  • +
  • +

    “One component per file” - the name of the file should match the data + attribute so that it can be found easily, a win for LoB

    +
  • +
+
+
+

To follow the RSJS guidelines, let’s restructure our current HTML and JavaScript files. + First, we will use data attributes, + that is, HTML attributes that begin with data-, a standard feature of HTML, to + indicate that our HTML is a counter + component. We will then update our JavaScript to use an attribute selector that looks for + the data-counter attribute + as the root element in our counter component and wires in the appropriate event handlers and + logic. Additionally, let’s + rework the code to use querySelectorAll() and add the counter functionality to + all counter components found on the + page. (You never know how many counter’s you might want!)

+
+
+

Here is what our code looks like now:

+
+
+
Counter in vanilla JavaScript, with RSJS
+
+
+
+
<section class="counter" data-counter> (1)
   <output id="my-output" data-counter-output>0</output> (2)
   <button class="increment-btn" data-counter-increment>Increment</button>
 </section>
-
-
-
-
    -
  1. -

    Invoke a JavaScript behavior with a data attribute

    -
  2. -
  3. -

    Mark relevant descendant elements

    -
  4. -
-
-
-
-
// counter.js (1)
+                                
+
+
+
    +
  1. +

    Invoke a JavaScript behavior with a data attribute

    +
  2. +
  3. +

    Mark relevant descendant elements

    +
  4. +
+
+
+
+
// counter.js (1)
 document.querySelectorAll("[data-counter]") (2)
   .forEach(el => {
     const
@@ -11199,73 +16674,85 @@ document.querySelectorAll("[data-counter]") (2)
 
     increment.addEventListener("click", e => output.textContent++); (4)
   });
-
-
-
-
    -
  1. -

    File should have the same name as the data attribute, so that we can locate it easily

    -
  2. -
  3. -

    Get all elements that invoke this behavior

    -
  4. -
  5. -

    Get any child elements we need

    -
  6. -
  7. -

    Register event handlers

    -
  8. -
-
-
-
-
-

Using RSJS solves, or at least alleviates, many of the problems we pointed out with our first, unstructured example of VanillaJS being -split out to a separate file:

-
-
-
    -
  • -

    The JS that attaches behavior to a given element is clear (though only through naming conventions).

    -
  • -
  • -

    Reuse is easy — you can create another counter component on the page and it will just work.

    -
  • -
  • -

    The code is well-organized — one behavior per file.

    -
  • -
-
-
-

All in all, RSJS is a good way to structure your vanilla JavaScript in a Hypermedia Driven Application. So long as the -JavaScript isn’t communicating with a server via a plain data JSON API, or holding a bunch of internal state outside of -the DOM, this is perfectly compatible with the HDA approach.

-
-
-

Let’s next take a look at implementing a feature in Contact.app using the RSJS/vanilla JavaScript approach.

-
-
-
-

VanillaJS in action: an overflow menu

-
-

Our homepage has “Edit”, “View” and “Delete” links for every contact in our table. This uses a lot of space and creates -visual clutter. Let’s fix that by placing these actions inside a drop-down menu with a button to open it.

-
-
-

Let’s begin by sketching the markup we want for our dropdown menu. First, we need an element, we’ll use a <div>, to enclose the -entire widget and mark it as a menu component. Within this div, we will have a standard <button> that will function -as the mechanism that shows and hides our menu items. Finally, we’ll have another <div> that holds the menu items -that we are going to show.

-
-
-

These menu items will be simple anchor tags, as they are in the current contacts table.

-
-
-

Here is what our updated, RSJS-structured HTML looks like:

-
-
-
-
<div data-overflow-menu> (1)
+                                
+
+
+
    +
  1. +

    File should have the same name as the data attribute, so that we can locate + it easily

    +
  2. +
  3. +

    Get all elements that invoke this behavior

    +
  4. +
  5. +

    Get any child elements we need

    +
  6. +
  7. +

    Register event handlers

    +
  8. +
+
+
+
+
+

Using RSJS solves, or at least alleviates, many of the problems we pointed out with our + first, unstructured example of VanillaJS being + split out to a separate file:

+
+
+
    +
  • +

    The JS that attaches behavior to a given element is clear (though only + through naming conventions).

    +
  • +
  • +

    Reuse is easy — you can create another counter component + on the page and it will just work.

    +
  • +
  • +

    The code is well-organized — one behavior per file.

    +
  • +
+
+
+

All in all, RSJS is a good way to structure your vanilla JavaScript in a Hypermedia Driven + Application. So long as the + JavaScript isn’t communicating with a server via a plain data JSON API, or holding a + bunch of internal state outside of + the DOM, this is perfectly compatible with the HDA approach.

+
+
+

Let’s next take a look at implementing a feature in Contact.app using the RSJS/vanilla + JavaScript approach.

+
+
+
+

VanillaJS in action: an overflow menu

+
+

Our homepage has “Edit”, “View” and “Delete” links for + every contact in our table. This uses a lot of space and creates + visual clutter. Let’s fix that by placing these actions inside a drop-down menu with a + button to open it.

+
+
+

Let’s begin by sketching the markup we want for our dropdown menu. First, we need an + element, we’ll use a <div>, to enclose the + entire widget and mark it as a menu component. Within this div, we will have a standard + <button> that will function + as the mechanism that shows and hides our menu items. Finally, we’ll have another + <div> that holds the menu items + that we are going to show.

+
+
+

These menu items will be simple anchor tags, as they are in the current contacts table.

+
+
+

Here is what our updated, RSJS-structured HTML looks like:

+
+
+
+
<div data-overflow-menu> (1)
     <button type="button" aria-haspopup="menu"
         aria-controls="contact-menu-{{ contact.id }}"
         >Options</button> (2)
@@ -11275,151 +16762,174 @@ that we are going to show.

<!-- ... --> </div> </div>
-
-
-
-
    -
  1. -

    Mark the root element of the menu component

    -
  2. -
  3. -

    This button will open and close our menu

    -
  4. -
  5. -

    A container for our menu items

    -
  6. -
  7. -

    Menu items

    -
  8. -
-
-
-

The roles and ARIA attributes are based on the Menu and Menu Button patterns from the ARIA Authoring Practices Guide.

-
- -
-

With this ARIA introduction out the way, let’s return to our VanillaJS drop down menu. We’ll begin with the RSJS -boilerplate: query for all elements with some data attribute, iterate over them, get any relevant descendants.

-
-
-

Note that, below, we’ve modified the RSJS boilerplate a bit to integrate with htmx, in particular we load the -overflow menu when htmx loads new content.

-
-
-
-
function overflowMenu(subtree = document) {
+                        
+
+
+
    +
  1. +

    Mark the root element of the menu component

    +
  2. +
  3. +

    This button will open and close our menu

    +
  4. +
  5. +

    A container for our menu items

    +
  6. +
  7. +

    Menu items

    +
  8. +
+
+
+

The roles and ARIA attributes are based on the Menu and Menu Button patterns from the ARIA + Authoring Practices Guide.

+
+ +
+

With this ARIA introduction out the way, let’s return to our VanillaJS drop down menu. + We’ll begin with the RSJS + boilerplate: query for all elements with some data attribute, iterate over them, get any + relevant descendants.

+
+
+

Note that, below, we’ve modified the RSJS boilerplate a bit to integrate with htmx, in + particular we load the + overflow menu when htmx loads new content.

+
+
+
+
function overflowMenu(subtree = document) {
   document.querySelectorAll("[data-overflow-menu]").forEach(menuRoot => { (1)
     const
     button = menuRoot.querySelector("[aria-haspopup]"), (2)
@@ -11429,80 +16939,90 @@ overflow menu when htmx loads new content.

} addEventListener("htmx:load", e => overflowMenu(e.target)); (4)
-
-
-
-
    -
  1. -

    With RSJS, you’ll be writing document.querySelectorAll(…​).forEach a lot.

    -
  2. -
  3. -

    To keep the HTML clean, we use ARIA attributes rather than custom data attributes here.

    -
  4. -
  5. -

    Use the spread operator to convert a NodeList into a normal Array.

    -
  6. -
  7. -

    Initialize all overflow menus when the page is loaded or content is inserted by htmx.

    -
  8. -
-
-
-

Conventionally, we would keep track of whether the menu is open using a JavaScript variable or a property in a JavaScript -state object. This approach is common in large, JavaScript-heavy web applications.

-
-
-

However, this approach has some drawback:

-
-
-
    -
  • -

    We would need to keep the DOM in sync with the state (harder without a framework)

    -
  • -
  • -

    We would lose the ability to serialize the HTML (as this open state isn’t stored in the DOM, but rather in JavaScript).

    -
  • -
-
-
-

Instead of taking this approach, we will use the DOM to store our state. We’ll lean on the hidden attribute on the -menu element to tell us it’s closed. If the HTML of the page is snapshotted and restored, the menu can be restored as -well by simply re-running the JS.

-
-
-
-
  items = [...menu.querySelectorAll("[role=menuitem]")];
+                        
+
+
+
    +
  1. +

    With RSJS, you’ll be writing + document.querySelectorAll(…​).forEach a lot.

    +
  2. +
  3. +

    To keep the HTML clean, we use ARIA attributes rather than custom data attributes + here.

    +
  4. +
  5. +

    Use the spread operator to convert a NodeList into a normal + Array.

    +
  6. +
  7. +

    Initialize all overflow menus when the page is loaded or content is inserted by htmx. +

    +
  8. +
+
+
+

Conventionally, we would keep track of whether the menu is open using a JavaScript variable + or a property in a JavaScript + state object. This approach is common in large, JavaScript-heavy web applications.

+
+
+

However, this approach has some drawback:

+
+
+
    +
  • +

    We would need to keep the DOM in sync with the state (harder without a framework)

    +
  • +
  • +

    We would lose the ability to serialize the HTML (as this open state isn’t + stored in the DOM, but rather in JavaScript).

    +
  • +
+
+
+

Instead of taking this approach, we will use the DOM to store our state. We’ll lean on + the hidden attribute on the + menu element to tell us it’s closed. If the HTML of the page is snapshotted and + restored, the menu can be restored as + well by simply re-running the JS.

+
+
+
+
  items = [...menu.querySelectorAll("[role=menuitem]")];
 
   const isOpen = () => !menu.hidden; (1)
 
 });
-
-
-
-
    -
  1. -

    The hidden attribute is helpfully reflected as a hidden property, so we don’t need to use getAttribute.

    -
  2. -
-
-
-

We’ll also make the menu items non-tabbable, so we can manage their focus ourselves.

-
-
-
-
  const isOpen = () => !menu.hidden; (1)
+                        
+
+
+
    +
  1. +

    The hidden attribute is helpfully reflected as a hidden + property, so we don’t need to use getAttribute.

    +
  2. +
+
+
+

We’ll also make the menu items non-tabbable, so we can manage their focus ourselves. +

+
+
+
+
  const isOpen = () => !menu.hidden; (1)
 
   items.forEach(item => item.setAttribute("tabindex", "-1"));
 
 });
-
-
-
-

Now let’s implement toggling the menu in JavaScript:

-
-
-
-
  items.forEach(item => item.setAttribute("tabindex", "-1"));
+                        
+
+
+

Now let’s implement toggling the menu in JavaScript:

+
+
+
+
  items.forEach(item => item.setAttribute("tabindex", "-1"));
 
   function toggleMenu(open = !isOpen()) { (1)
     if (open) {
@@ -11520,80 +17040,94 @@ well by simply re-running the JS.

menuRoot.addEventListener("blur", e => toggleMenu(false)); (5) })
-
-
-
-
    -
  1. -

    Optional parameter to specify desired state. This allows us to use one function to open, close, or toggle the menu.

    -
  2. -
  3. -

    Focus first item of menu when opened.

    -
  4. -
  5. -

    Call toggleMenu with current state, to initialize element attributes.

    -
  6. -
  7. -

    Toggle menu when button is clicked.

    -
  8. -
  9. -

    Close menu when focus moves away.

    -
  10. -
-
-
-

Let’s also make the menu close when we click outside it, a nice behavior that mimics how native drop-down menus work. This -will require an event listener on the whole window.

-
-
-

Note that we need to be careful with this kind of listener: you may find that listeners accumulate as components add -listeners and fail to remove them when the component is removed from the DOM. This, unfortunately, leads to difficult -to track down memory leaks.

-
-
-

There is not an easy way in JavaScript to execute logic when an element is removed. The best option is what is known -as the MutationObserver API. A MutationObserver is very useful, but the API is quite heavy and a bit arcane, so we -won’t be using it for our example.

-
-
-

Instead, we will use a simple pattern to avoid leaking event listeners: when our event listener runs, we will check if the -attaching component is still in the DOM, and, if the element is no longer in the DOM, we will remove the listener and -exit.

-
-
-

This is a somewhat hacky, manual form of garbage collection. As is (usually) the case with other garbage collection -algorithms, our strategy removes listeners in a nondeterministic amount of time after they are no longer needed. Fortunately -for us, With a frequent event like “the user clicks anywhere in the page” driving the collection, it should work well -enough for our system.

-
-
-
-
  menuRoot.addEventListener("blur", e => toggleMenu(false));
+                        
+
+
+
    +
  1. +

    Optional parameter to specify desired state. This allows us to use one function to + open, close, or toggle the menu.

    +
  2. +
  3. +

    Focus first item of menu when opened.

    +
  4. +
  5. +

    Call toggleMenu with current state, to initialize element attributes. +

    +
  6. +
  7. +

    Toggle menu when button is clicked.

    +
  8. +
  9. +

    Close menu when focus moves away.

    +
  10. +
+
+
+

Let’s also make the menu close when we click outside it, a nice behavior that mimics + how native drop-down menus work. This + will require an event listener on the whole window.

+
+
+

Note that we need to be careful with this kind of listener: you may find that listeners + accumulate as components add + listeners and fail to remove them when the component is removed from the DOM. This, + unfortunately, leads to difficult + to track down memory leaks.

+
+
+

There is not an easy way in JavaScript to execute logic when an element is removed. The best + option is what is known + as the MutationObserver API. A MutationObserver is very useful, + but the API is quite heavy and a bit arcane, so we + won’t be using it for our example.

+
+
+

Instead, we will use a simple pattern to avoid leaking event listeners: when our event + listener runs, we will check if the + attaching component is still in the DOM, and, if the element is no longer in the DOM, we + will remove the listener and + exit.

+
+
+

This is a somewhat hacky, manual form of garbage collection. As is (usually) the + case with other garbage collection + algorithms, our strategy removes listeners in a nondeterministic amount of time after they + are no longer needed. Fortunately + for us, With a frequent event like “the user clicks anywhere in the page” + driving the collection, it should work well + enough for our system.

+
+
+
+
  menuRoot.addEventListener("blur", e => toggleMenu(false));
 
   window.addEventListener("click", function clickAway(event) {
     if (!menuRoot.isConnected) window.removeEventListener("click", clickAway); (1)
     if (!menuRoot.contains(event.target)) toggleMenu(false); (2)
   });
 });
-
-
-
-
    -
  1. -

    This line is the garbage collection

    -
  2. -
  3. -

    If the click is outside the menu, close the menu

    -
  4. -
-
-
-

Now, let’s move on to the keyboard interactions for our dropdown menu. The keyboard handlers turn out to all be pretty -similar to one another and not particularly intricate, so let’s knock them all out in one go:

-
-
-
-
    if (!menuRoot.contains(event.target)) toggleMenu(false); (2)
+                        
+
+
+
    +
  1. +

    This line is the garbage collection

    +
  2. +
  3. +

    If the click is outside the menu, close the menu

    +
  4. +
+
+
+

Now, let’s move on to the keyboard interactions for our dropdown menu. The keyboard + handlers turn out to all be pretty + similar to one another and not particularly intricate, so let’s knock them all out in + one go:

+
+
+
+
    if (!menuRoot.contains(event.target)) toggleMenu(false); (2)
   });
 
   const currentIndex = () => { (1)
@@ -11624,250 +17158,306 @@ similar to one another and not particularly intricate, so let’s knock them
     }
   });
 });
-
-
-
-
    -
  1. -

    Helper: Get the index in the items array of the currently focused menu item (0 if none).

    -
  2. -
  3. -

    Move focus to the previous menu item when the up arrow key is pressed

    -
  4. -
  5. -

    Move focus to the next menu item when the down arrow key is pressed

    -
  6. -
  7. -

    Activate the currently focused element when the space key is pressed

    -
  8. -
  9. -

    Move focus to the first menu item when Home is pressed

    -
  10. -
  11. -

    Move focus to the last menu item when End is pressed

    -
  12. -
  13. -

    Close menu when Escape is pressed

    -
  14. -
  15. -

    Return focus to menu button when closing menu

    -
  16. -
-
-
-

That should cover all our bases, and we’ll admit that that’s a lot of code. But, in fairness, it’s code that encodes a -lot of behavior.

-
-
-

Now, our drop-down menu isn’t perfect, and it doesn’t handle a lot of things. For example, we don’t support submenus, -or menu items being added or removed dynamically to the menu. If we needed more menu features like this, it might make -more sense to use an off-the-shelf library such as, GitHub’s details-menu-element.

-
-
-

But, for our relatively simple use case, this library does a fine job, and we got to explore ARIA and RSJS while -implementing it.

-
-
-
-
-

10.5. Alpine.js

-
-

OK, so that’s an in-depth look at how to structure plain VanillaJS-style JavaScript. Let’s turn our attention to an -actual JavaScript framework that enables a different approach for adding dynamic behavior to your application, -Alpine.js.

-
-
-

Alpine is a relatively new JavaScript library which allows developers to embed JavaScript code directly in HTML, akin to -the on* attributes available in plain HTML and JavaScript. However, Alpine takes this concept of embedded scripting -much further than on* attributes.

-
-
-

Alpine bills itself as a modern replacement for jQuery, the widely used, older JavaScript library. As you will see, it -definitely lives up to this promise.

-
-
-

Installing Alpine is very easy: it is a single file and is dependency-free, so you can simply include it via a CDN:

-
-
-
Installing Alpine
-
-
<script src="https://unpkg.com/alpinejs"></script>
-
-
-
-

You can also install it via a package manager such as NPM, or vendor it from your own server.

-
-
-

Alpine provides a set of HTML attributes, all of which begin with the x- prefix, the main one of which is x-data. -The content of x-data is a JavaScript expression which evaluates to an object. The properties of this object can, then, -be access within the element that the x-data attribute is located on.

-
-
-

To get a flavor of what AlpineJS looks like, let’s look at how to implement our counter example using it.

-
-
-

For the counter, the only state we need to keep track of is the current number, so let’s declare a JavaScript object -with one property, count, in an x-data attribute on the div for our counter:

-
-
-
Counter with Alpine, line 1
-
-
<div class="counter" x-data="{ count: 0 }">
-
-
-
-

This defines our state, that is, the data we are going to be using to drive dynamic updates to the DOM. With the state -declared like this, we can now use it within the div element it is declared on. Let’s add an output element with -an x-text attribute.

-
-
-

Next, we will bind the x-text attribute to the count attribute we declared in the x-data attribute -on the parent div element. This will have the effect of setting the text of the output element to whatever the -value of count is: if count is updated, so will the text of the output. This is “reactive” programming, in that -the DOM will “react” to changes to the backing data.

-
-
-
Counter with Alpine, lines 1-2
-
-
<div class="counter" x-data="{ count: 0 }">
+                        
+
+
+
    +
  1. +

    Helper: Get the index in the items array of the currently focused menu item (0 if + none).

    +
  2. +
  3. +

    Move focus to the previous menu item when the up arrow key is pressed

    +
  4. +
  5. +

    Move focus to the next menu item when the down arrow key is pressed

    +
  6. +
  7. +

    Activate the currently focused element when the space key is pressed

    +
  8. +
  9. +

    Move focus to the first menu item when Home is pressed

    +
  10. +
  11. +

    Move focus to the last menu item when End is pressed

    +
  12. +
  13. +

    Close menu when Escape is pressed

    +
  14. +
  15. +

    Return focus to menu button when closing menu

    +
  16. +
+
+
+

That should cover all our bases, and we’ll admit that that’s a lot of code. But, + in fairness, it’s code that encodes a + lot of behavior.

+
+
+

Now, our drop-down menu isn’t perfect, and it doesn’t handle a lot of things. For + example, we don’t support submenus, + or menu items being added or removed dynamically to the menu. If we needed more menu + features like this, it might make + more sense to use an off-the-shelf library such as, GitHub’s details-menu-element. +

+
+
+

But, for our relatively simple use case, this library does a fine job, and we got to explore + ARIA and RSJS while + implementing it.

+
+
+
+
+

10.5. Alpine.js

+
+

OK, so that’s an in-depth look at how to structure plain VanillaJS-style JavaScript. + Let’s turn our attention to an + actual JavaScript framework that enables a different approach for adding dynamic behavior to + your application, + Alpine.js. +

+
+
+

Alpine is a relatively new JavaScript library which allows developers to embed JavaScript code + directly in HTML, akin to + the on* attributes available in plain HTML and JavaScript. However, Alpine takes + this concept of embedded scripting + much further than on* attributes.

+
+
+

Alpine bills itself as a modern replacement for jQuery, the widely used, older JavaScript + library. As you will see, it + definitely lives up to this promise.

+
+
+

Installing Alpine is very easy: it is a single file and is dependency-free, so you can simply + include it via a CDN:

+
+
+
Installing Alpine
+
+
<script src="https://unpkg.com/alpinejs"></script>
+
+
+
+

You can also install it via a package manager such as NPM, or vendor it from your own server.

+
+
+

Alpine provides a set of HTML attributes, all of which begin with the x- prefix, the + main one of which is x-data. + The content of x-data is a JavaScript expression which evaluates to an object. The + properties of this object can, then, + be access within the element that the x-data attribute is located on.

+
+
+

To get a flavor of what AlpineJS looks like, let’s look at how to implement our counter + example using it.

+
+
+

For the counter, the only state we need to keep track of is the current number, so let’s + declare a JavaScript object + with one property, count, in an x-data attribute on the div for our + counter:

+
+
+
Counter with Alpine, line 1
+
+
<div class="counter" x-data="{ count: 0 }">
+
+
+
+

This defines our state, that is, the data we are going to be using to drive dynamic updates to + the DOM. With the state + declared like this, we can now use it within the div element it is declared on. + Let’s add an output element with + an x-text attribute.

+
+
+

Next, we will bind the x-text attribute to the count attribute + we declared in the x-data attribute + on the parent div element. This will have the effect of setting the text of the + output element to whatever the + value of count is: if count is updated, so will the text of the + output. This is “reactive” programming, in that + the DOM will “react” to changes to the backing data.

+
+
+
Counter with Alpine, lines 1-2
+
+
<div class="counter" x-data="{ count: 0 }">
   <output x-text="count"></output> (1)
-
-
-
-
    -
  1. -

    The x-text attribute.

    -
  2. -
-
-
-

Next, we need to update the count, using a button. Alpine allows you to attach event listeners with the x-on attribute.

-
-
-

To specify the event to listen for, you add a colon and then the event name after the x-on attribute name. Then, the -value of the attribute is the JavaScript you wish to execute. This is similar to the plain on* attributes we discussed -earlier, but it turns out to be much more flexible.

-
-
-

We want to listen for a click event, and we want to increment count when a click occurs, so here is what the Alpine -code will look like:

-
-
-
Counter with Alpine, the full thing
-
-
<div class="counter" x-data="{ count: 0 }">
+                    
+
+
+
    +
  1. +

    The x-text attribute.

    +
  2. +
+
+
+

Next, we need to update the count, using a button. Alpine allows you to attach event listeners + with the x-on attribute.

+
+
+

To specify the event to listen for, you add a colon and then the event name after the + x-on attribute name. Then, the + value of the attribute is the JavaScript you wish to execute. This is similar to the plain + on* attributes we discussed + earlier, but it turns out to be much more flexible.

+
+
+

We want to listen for a click event, and we want to increment count + when a click occurs, so here is what the Alpine + code will look like:

+
+
+
Counter with Alpine, the full thing
+
+
<div class="counter" x-data="{ count: 0 }">
   <output x-text="count"></output>
 
   <button x-on:click="count++">Increment</button> (1)
 </div>
-
-
-
-
    -
  1. -

    With x-on, we specify the attribute in the attribute name.

    -
  2. -
-
-
-

And that’s all it takes. A simple component like a counter should be simple, and Alpine delivers.

-
-
-

x-on:click vs. onclick

-
-

As we said, the Alpine x-on:click attribute (or its shorthand, the @click attribute) is similar to the built-in -onclick attribute. However, it has additional features that make it significantly more useful:

-
-
-
    -
  • -

    You can listen for events from other elements. For example, the .outside modifier lets you listen to any click event -that is not within the element.

    -
  • -
  • -

    You can use other modifiers to:

    -
    -
      -
    • -

      throttle or debounce event listeners,

      -
    • -
    • -

      ignore events that are bubbled up from descendant elements, or

      -
    • -
    • -

      attach passive listeners.

      -
    • -
    -
    -
  • -
  • -

    You can listen to custom events. For example, if you wanted to listen for the htmx:after-request event you could write -x-on:htmx:after-request="doSomething()"

    -
  • -
-
-
-
-

Reactivity and Templating

-
-

We hope that you’ll agree that the AlpineJS version of the counter widget is better, in general, than the VanillaJS -implementation, which was either somewhat hacky or spread out over multiple files.

-
-
-

A big part of the power of AlpineJS is that it supports a notion of “reactive” variables, allowing you to bind the count -of the div element to a variable that both the output and the button can reference, and properly updating all the -dependencies when a mutation occurs. Alpine allows for much more elaborate data bindings than what we have demonstrated -here, and it is an excellent general purpose client-side scripting library.

-
-
-
-

Alpine.js in Action: A Bulk Action Toolbar

-
-

Next, let’s implement a feature in Contact.app with Alpine. As it stands currently, Contact.app has a “Delete Selected -Contacts” button at the very bottom of the page. This button has a long name, is not easy to find and takes up a -lot of room. If we wanted to add additional “bulk” actions, this wouldn’t really scale very well visually.

-
-
-

In this section, we’ll replace this single button with a toolbar. Furthermore, the toolbar will only appear when the -user starts selecting contacts. Finally, it will show how many contacts are selected and let you select all contacts in -one go.

-
-
-

The first thing we will need to add is an x-data attribute, to hold the state that we will use to determine if the -toolbar is visible or not. We will need to place this on a parent element of both the toolbar that we are going to -add, as well as the checkboxes, which will be updating the state when they are checked and unchecked. The best -option given our current HTML is to place the attribute on the form element that surrounds the contacts table. We -will declare a property, selected, which will be an array that holds the selected contact ids, based on the checkboxes -that are selected.

-
-
-

Here is what our form tag will look like:

-
-
-
-
<form x-data="{ selected: [] }"> (1)
-
-
-
-
    -
  1. -

    This is the form that was wrapped around the contacts table.

    -
  2. -
-
-
-

Next, at the top of the contacts table, we are going to add a template tag. A template tag is not rendered by a -browser, by default, so you might be surprised that we are using it. However, by adding an Alpine x-if attribute, -we can tell Alpine: if a condition is true, show the HTML within this template.

-
-
-

Recall that we want to show the toolbar if and only if one or more contacts are selected. But we know that we will -have the ids of the selected contacts in the selected property. Therefore, we can check the length of that array -to see if there are any selected contacts, quite easily:

-
-
-
-
<template x-if="selected.length > 0"> (1)
+                    
+
+
+
    +
  1. +

    With x-on, we specify the attribute in the attribute name.

    +
  2. +
+
+
+

And that’s all it takes. A simple component like a counter should be simple, and Alpine + delivers.

+
+
+

x-on:click vs. onclick

+
+

As we said, the Alpine x-on:click attribute (or its shorthand, the + @click attribute) is similar to the built-in + onclick attribute. However, it has additional features that make it + significantly more useful: +

+
+
+
    +
  • +

    You can listen for events from other elements. For example, the .outside + modifier lets you listen to any click event + that is not within the element.

    +
  • +
  • +

    You can use other modifiers to:

    +
    +
      +
    • +

      throttle or debounce event listeners,

      +
    • +
    • +

      ignore events that are bubbled up from descendant elements, or

      +
    • +
    • +

      attach passive listeners.

      +
    • +
    +
    +
  • +
  • +

    You can listen to custom events. For example, if you wanted to listen for the + htmx:after-request event you could write + x-on:htmx:after-request="doSomething()" +

    +
  • +
+
+
+
+

Reactivity and Templating

+
+

We hope that you’ll agree that the AlpineJS version of the counter widget is better, in + general, than the VanillaJS + implementation, which was either somewhat hacky or spread out over multiple files.

+
+
+

A big part of the power of AlpineJS is that it supports a notion of “reactive” + variables, allowing you to bind the count + of the div element to a variable that both the output and the + button can reference, and properly updating all the + dependencies when a mutation occurs. Alpine allows for much more elaborate data bindings + than what we have demonstrated + here, and it is an excellent general purpose client-side scripting library.

+
+
+
+

Alpine.js in Action: A Bulk Action Toolbar

+
+

Next, let’s implement a feature in Contact.app with Alpine. As it stands currently, + Contact.app has a “Delete Selected + Contacts” button at the very bottom of the page. This button has a long name, is not + easy to find and takes up a + lot of room. If we wanted to add additional “bulk” actions, this wouldn’t + really scale very well visually.

+
+
+

In this section, we’ll replace this single button with a toolbar. Furthermore, the + toolbar will only appear when the + user starts selecting contacts. Finally, it will show how many contacts are selected and let + you select all contacts in + one go.

+
+
+

The first thing we will need to add is an x-data attribute, to hold the state + that we will use to determine if the + toolbar is visible or not. We will need to place this on a parent element of both the + toolbar that we are going to + add, as well as the checkboxes, which will be updating the state when they are checked and + unchecked. The best + option given our current HTML is to place the attribute on the form element + that surrounds the contacts table. We + will declare a property, selected, which will be an array that holds the + selected contact ids, based on the checkboxes + that are selected.

+
+
+

Here is what our form tag will look like:

+
+
+
+
<form x-data="{ selected: [] }"> (1)
+
+
+
+
    +
  1. +

    This is the form that was wrapped around the contacts table.

    +
  2. +
+
+
+

Next, at the top of the contacts table, we are going to add a template tag. A + template tag is not rendered by a + browser, by default, so you might be surprised that we are using it. However, by adding an + Alpine x-if attribute, + we can tell Alpine: if a condition is true, show the HTML within this template.

+
+
+

Recall that we want to show the toolbar if and only if one or more contacts are selected. But + we know that we will + have the ids of the selected contacts in the selected property. Therefore, we + can check the length of that array + to see if there are any selected contacts, quite easily:

+
+
+
+
<template x-if="selected.length > 0"> (1)
   <div class="box info tool-bar">
     <slot x-text="selected.length"></slot>
     contacts selected
@@ -11877,473 +17467,605 @@ to see if there are any selected contacts, quite easily:

<button type="button">Cancel</button> </div> </template>
-
-
-
-
    -
  1. -

    Show this HTML if there are 1 or more selected contacts

    -
  2. -
  3. -

    We will implement these buttons in just a moment

    -
  4. -
-
-
-

The next step is to ensure that toggling a checkbox for a given contact adds (or removes) a given contact’s id from the -selected property. To do this, we will need to use a new Alpine attribute, x-model. The x-model attribute allows -you to bind a given element to some underlying data, or its "model".

-
-
-

In this case, we want to bind the value of the checkbox inputs to the selected property. This is how we do this:

-
-
-
-
<td>
+                        
+
+
+
    +
  1. +

    Show this HTML if there are 1 or more selected contacts

    +
  2. +
  3. +

    We will implement these buttons in just a moment

    +
  4. +
+
+
+

The next step is to ensure that toggling a checkbox for a given contact adds (or removes) a + given contact’s id from the + selected property. To do this, we will need to use a new Alpine attribute, + x-model. The x-model attribute allows + you to bind a given element to some underlying data, or its "model". +

+
+
+

In this case, we want to bind the value of the checkbox inputs to the selected + property. This is how we do this:

+
+
+
+
<td>
 <input type="checkbox" name="selected_contact_ids" value="{{ contact.id }}" x-model="selected"> (1)
 </td>
-
-
-
-
    -
  1. -

    The x-model attribute binds the value of this input to the selected property

    -
  2. -
-
-
-

Now, when a checkbox is checked or unchecked, the selected array will be updated with the given row’s contact id. -Furthermore, mutations we make to the selected array will similarly be reflected in the checkboxes' state. This is -known as a two-way binding.

-
-
-

With this code written, we can make the toolbar appear and disappear, based on whether contact checkboxes are selected.

-
-
-

Very slick.

-
-
-
Implementing Actions
-
-

Now that we have the mechanics of showing and hiding the toolbar, let’s look at how to implement the buttons within -the toolbar.

-
-
-

Let’s first implement the “Clear” button, because it is quite easy. All we need to do is, when the button is clicked, -clear out the selected array. Because of the two-way binding that Alpine provides, this will uncheck all the selected -contacts (and then hide the toolbar)!

-
-
-

Here is the code:

-
-
-

For the Cancel button, our job is quite simple:

-
-
-
-
<button type="button" @click="selected = []">Cancel</button>(1)
-
-
-
-
    -
  1. -

    Just reset the selected array

    -
  2. -
-
-
-

Once again, AlpineJS makes this very easy.

-
-
-

The “Delete” button, however, will be a bit more complicated. It will need to do two things: first it will confirm -if the user indeed intends to delete the contacts selected, and, if the user confirms the action, it will use the -htmx JavaScript API to issue a DELETE request.

-
-
-
-
<button type="button" class="bad bg color border"
+                        
+
+
+
    +
  1. +

    The x-model attribute binds the value of this input to the + selected property

    +
  2. +
+
+
+

Now, when a checkbox is checked or unchecked, the selected array will be updated + with the given row’s contact id. + Furthermore, mutations we make to the selected array will similarly be + reflected in the checkboxes' state. This is + known as a two-way binding.

+
+
+

With this code written, we can make the toolbar appear and disappear, based on whether + contact checkboxes are selected.

+
+
+

Very slick.

+
+
+
Implementing Actions
+
+

Now that we have the mechanics of showing and hiding the toolbar, let’s look at how + to implement the buttons within + the toolbar.

+
+
+

Let’s first implement the “Clear” button, because it is quite easy. All + we need to do is, when the button is clicked, + clear out the selected array. Because of the two-way binding that Alpine + provides, this will uncheck all the selected + contacts (and then hide the toolbar)!

+
+
+

Here is the code:

+
+
+

For the Cancel button, our job is quite simple:

+
+
+
+
<button type="button" @click="selected = []">Cancel</button>(1)
+
+
+
+
    +
  1. +

    Just reset the selected array

    +
  2. +
+
+
+

Once again, AlpineJS makes this very easy.

+
+
+

The “Delete” button, however, will be a bit more complicated. It will need to + do two things: first it will confirm + if the user indeed intends to delete the contacts selected, and, if the user confirms + the action, it will use the + htmx JavaScript API to issue a DELETE request.

+
+
+
+
<button type="button" class="bad bg color border"
   @click="confirm(`Delete ${selected.length} contacts?`) && (1)
     htmx.ajax('DELETE', '/contacts', { source: $root, target: document.body })" (2)
 >Delete</button>
-
-
-
-
    -
  1. -

    Confirm the user wishes to delete the selected number of contacts

    -
  2. -
  3. -

    Issue a DELETE using the htmx JavaScript API

    -
  4. -
-
-
-

Note that we are using the short-circuiting behavior of the && operator in JavaScript to avoid the call to -htmx.ajax() if the confirm() call returns false.

-
-
-

The htmx.ajax() function is just a way to access the normal, HTML-driven hypermedia exchange that htmx’s -HTML attributes give you directly from JavaScript.

-
-
-

Looking at how we call htmx.ajax, we first pass in that we want to issue a DELETE to /contacts. We then pass in -two additional pieces of information: source and target. The source property is the element from which htmx will -collect data to include in the request. We set this to $root, which is a special symbol in Alpine that will be -the element that has the x-data attribute declared on it. In this case, it will be the form containing all of our -contacts. The target, or where the response HTML will be placed, is just the entire document’s body, since the -DELETE handler returns a whole page when it completes.

-
-
-

Note that we are using Alpine here in a Hypermedia Driven Application compatible manner. We could have issued an -AJAX request directly from Alpine and perhaps updated an x-data property depending on the results of that request. -But, instead, we delegated to htmx’s JavaScript API, which made a hypermedia exchange with the server.

-
-
-

This is the key to scripting in a hypermedia-friendly manner within a Hypermedia-Driven Application.

-
-
-

So, with all of this in place, we now have a much improved experience for performing bulk actions on contacts: less -visual clutter and the toolbar can be extended with more options without creating bloat in the main interface of our app.

-
-
-
-
-
-

10.6. _hyperscript

-
-

The final scripting technology we are going to look at is a bit further afield: _hyperscript.

-
-
-

While the previous two examples are JavaScript-oriented, _hyperscript is an entirely new scripting language for front-end -development. _hyperscript has a completely different syntax than JavaScript, based on an older language called HyperTalk. -HyperTalk was the scripting language for a technology called HyperCard, an old hypermedia system available on early -Macintosh Computers.

-
-
-

The most noticeable thing about _hyperscript is that it resembles English prose more than it resembles other programming -languages. _hyperscript was initially created as a sibling project to htmx, because we felt that JavaScript wasn’t -event-oriented enough, which made adding small scripting enhancements to htmx applications cumbersome. Like Alpine, -_hyperscript is a modern jQuery replacement.

-
-
-

Also like Alpine, _hyperscript allows you to write your scripting inline, in HTML.

-
-
-

Unlike Alpine, however, _hyperscript is not reactive. It instead focuses on making DOM manipulations in response to events, -easy to write and easy to read. It has built-in language constructs for many DOM operations, preventing you from needing -to navigate the sometimes-verbose JavaScript DOM APIs.

-
-
-

We will not be doing a deep dive on the language, but again just want to give you a flavor of what scripting in -_hyperscript is like, so you can pursue the language in more depth later if you find it interesting.

-
-
-

Like htmx and AlpineJS, _hyperscript can be installed via a CDN or from npm (package name hyperscript.org):

-
-
-
Installing _hyperscript via CDN
-
-
<script src="//unpkg.com/hyperscript.org"></script>
-
-
-
-

_hyperscript uses the \_ (underscore) attribute for putting scripting on DOM elements. You may also use the script -or data-script attributes, depending on your HTML validation needs.

-
-
-

Let’s look at how to implement the simple counter component we have been looking at using _hyperscript. We will place - an output element and a button inside of a div. To implement the counter, we will need to add a small bit of -_hyperscript to the button. On a click, the button should increment the text of the previous output tag.

-
-
-

It turns out that that last sentence is nearly valid _hyperscript!

-
-
-

Here is our code:

-
-
-
-
<div class="counter">
+                            
+
+
+
    +
  1. +

    Confirm the user wishes to delete the selected number of contacts

    +
  2. +
  3. +

    Issue a DELETE using the htmx JavaScript API

    +
  4. +
+
+
+

Note that we are using the short-circuiting behavior of the && + operator in JavaScript to avoid the call to + htmx.ajax() if the confirm() call returns false. +

+
+
+

The htmx.ajax() function is just a way to access the normal, HTML-driven + hypermedia exchange that htmx’s + HTML attributes give you directly from JavaScript.

+
+
+

Looking at how we call htmx.ajax, we first pass in that we want to issue a + DELETE to /contacts. We then pass in + two additional pieces of information: source and target. The + source property is the element from which htmx will + collect data to include in the request. We set this to $root, which is a + special symbol in Alpine that will be + the element that has the x-data attribute declared on it. In this case, it + will be the form containing all of our + contacts. The target, or where the response HTML will be placed, is just + the entire document’s body, since the + DELETE handler returns a whole page when it completes. +

+
+
+

Note that we are using Alpine here in a Hypermedia Driven Application compatible manner. + We could have issued an + AJAX request directly from Alpine and perhaps updated an x-data property + depending on the results of that request. + But, instead, we delegated to htmx’s JavaScript API, which made a hypermedia + exchange with the server.

+
+
+

This is the key to scripting in a hypermedia-friendly manner within a Hypermedia-Driven + Application.

+
+
+

So, with all of this in place, we now have a much improved experience for performing bulk + actions on contacts: less + visual clutter and the toolbar can be extended with more options without creating bloat + in the main interface of our app.

+
+
+
+
+
+

10.6. _hyperscript

+
+

The final scripting technology we are going to look at is a bit further afield: _hyperscript.

+
+
+

While the previous two examples are JavaScript-oriented, _hyperscript is an entirely new + scripting language for front-end + development. _hyperscript has a completely different syntax than JavaScript, based on an older + language called HyperTalk. + HyperTalk was the scripting language for a technology called HyperCard, an old hypermedia system + available on early + Macintosh Computers.

+
+
+

The most noticeable thing about _hyperscript is that it resembles English prose more than it + resembles other programming + languages. _hyperscript was initially created as a sibling project to htmx, because we felt that + JavaScript wasn’t + event-oriented enough, which made adding small scripting enhancements to htmx applications + cumbersome. Like Alpine, + _hyperscript is a modern jQuery replacement.

+
+
+

Also like Alpine, _hyperscript allows you to write your scripting inline, in HTML.

+
+
+

Unlike Alpine, however, _hyperscript is not reactive. It instead focuses on making DOM + manipulations in response to events, + easy to write and easy to read. It has built-in language constructs for many DOM operations, + preventing you from needing + to navigate the sometimes-verbose JavaScript DOM APIs.

+
+
+

We will not be doing a deep dive on the language, but again just want to give you a flavor of + what scripting in + _hyperscript is like, so you can pursue the language in more depth later if you find it + interesting.

+
+
+

Like htmx and AlpineJS, _hyperscript can be installed via a CDN or from npm (package name + hyperscript.org):

+
+
+
Installing _hyperscript via CDN
+
+
<script src="//unpkg.com/hyperscript.org"></script>
+
+
+
+

_hyperscript uses the \_ (underscore) attribute for putting scripting on DOM + elements. You may also use the script + or data-script attributes, depending on your HTML validation needs.

+
+
+

Let’s look at how to implement the simple counter component we have been looking at using + _hyperscript. We will place + an output element and a button inside of a div. To + implement the counter, we will need to add a small bit of + _hyperscript to the button. On a click, the button should increment the text of the previous + output tag.

+
+
+

It turns out that that last sentence is nearly valid _hyperscript!

+
+
+

Here is our code:

+
+
+
+
<div class="counter">
   <output>0</output>
   <button _="on click increment the textContent of the previous <output/>">Increment</button> (1)
 </div>
-
-
-
-
    -
  1. -

    This is what _hyperscript looks like, believe it or not

    -
  2. -
-
-
-

Let’s go through each component of this script:

-
-
-
    -
  • -

    on click This is an event listener, telling the button to listen for a click event and then executing -the remaining code

    -
  • -
  • -

    increment This is a “command” in _hyperscript that “increments” things, similar to the ++ operator in JavaScript

    -
  • -
  • -

    the “the” doesn’t have any semantic meaning _hyperscript, but can used to make scripts more readable

    -
  • -
  • -

    textContent of - This one form of property access in _hyperscript. You are probably familiar with the JavaScript -syntax a.b, meaning "Get the property b on object a`". _hyperscript supports this syntax, but also supports -the forms `b of a and a’s b. Which one you use should depend on which one is most readable.

    -
  • -
  • -

    the previous The previous expression in _hyperscript finds the previous element in the DOM that matches some condition

    -
  • -
  • -

    <output /> This is a query literal, which is a CSS selector wrapped between < and />

    -
  • -
-
-
-

In this code, the previous keyword (and the accompanying next keyword) is an example of how _hyperscript makes DOM operations -easier: there is no such native functionality to be found in the standard DOM API, and implementing this in VanillaJS is trickier -than you might think!

-
-
-

So, you can see, _hyperscript is very expressive, particularly when it comes to DOM manipulations. This makes it -easier to embed scripts directly in HTML: since the scripting language is more powerful, scripts written in it tend -to be shorter and easier to read.

-
- -
-

_hyperscript in action: a keyboard shortcut

-
-

While the counter demo is a good way to compare various approaches to scripting, the rubber meets the road when -you try to actually implement a useful feature with an approach. For _hyperscript, let’s add a keyboard shortcut -to Contact.app: when a user hits Shift-S in our app, we will focus the search field.

-
-
-

Since our keyboard shortcut focuses the search input, let’s put the code for it on that search input, satisfying -locality.

-
-
-

Here is the original HTML for the search input:

-
-
-
-
<input id="search" name="q" type="search" placeholder="Search Contacts">
-
-
-
-

We will add an event listener using the on keydown syntax, which will fire whenever a keydown occurs. Further, we -can use an event filter syntax in _hyperscript using square brackets after the event. In the square brackets we -can place a filter expression that will filter out keydown events we aren’t interested in. In our case, we only -want to consider events where the shift key is held down and where the “S” key is being pressed. We can create a -boolean expression that inspects the shiftKey property (to see if it is true) and the code property (to see if -it is "KeyS") of the event to achieve this.

-
-
-

So far our _hyperscript looks like this:

-
-
-
A Start On Our Keyboard Shortcut
-
-
  on keydown[shiftKey and code is 'KeyS'] ...
-
-
-
-

Now, by default, _hyperscript will listen for a given event on the element it is declared on. So, in this case, with -the script we have so far, we would only get keydown events if the search box is already focused. That’s not what -we want! We want to have this key work globally, no matter which element has focus.

-
-
-

Not a problem! We can listen for the keyDown event elsewhere by using a from clause in our event handler. In this -case we want to listen for the keyDown from the window, and our code ends up looking, naturally, like this:

-
-
-
Listening Globally
-
-
  on keydown[shiftKey and code is 'KeyS'] from window ...
-
-
-
-

Using the from clause, we can attach the listener to the window while, at the same time, keeping the code on the -element it logically relates to.

-
-
-

Now that we’ve picked out the event we want to use to focus the search box, let’s implement the actual focusing by -calling the standard .focus() method.

-
-
-

Here is the entire script, embedded in HTML

-
-
-
Our Final Script
-
-
<input id="search" name="q" type="search" placeholder="Search Contacts"
+                    
+
+
+
    +
  1. +

    This is what _hyperscript looks like, believe it or not

    +
  2. +
+
+
+

Let’s go through each component of this script:

+
+
+
    +
  • +

    on click This is an event listener, telling the button to listen for a + click event and then executing + the remaining code

    +
  • +
  • +

    increment This is a “command” in _hyperscript that + “increments” things, similar to the ++ operator in JavaScript +

    +
  • +
  • +

    the “the” doesn’t have any semantic meaning _hyperscript, but can used + to make scripts more readable

    +
  • +
  • +

    textContent of - This one form of property access in _hyperscript. + You are probably familiar with the JavaScript + syntax a.b, meaning "Get the property b on object a`". _hyperscript supports this syntax, but also supports +the forms `b of a and a’s b. Which one you use should depend on which one is most readable. +

    +
  • +
  • +

    the previous The previous expression in _hyperscript finds the + previous element in the DOM that matches some condition

    +
  • +
  • +

    <output /> This is a query literal, which is a CSS selector + wrapped between < and />

    +
  • +
+
+
+

In this code, the previous keyword (and the accompanying next keyword) + is an example of how _hyperscript makes DOM operations + easier: there is no such native functionality to be found in the standard DOM API, and + implementing this in VanillaJS is trickier + than you might think!

+
+
+

So, you can see, _hyperscript is very expressive, particularly when it comes to DOM + manipulations. This makes it + easier to embed scripts directly in HTML: since the scripting language is more powerful, scripts + written in it tend + to be shorter and easier to read.

+
+ +
+

_hyperscript in action: a keyboard shortcut

+
+

While the counter demo is a good way to compare various approaches to scripting, the rubber + meets the road when + you try to actually implement a useful feature with an approach. For _hyperscript, + let’s add a keyboard shortcut + to Contact.app: when a user hits Shift-S in our app, we will focus the search field.

+
+
+

Since our keyboard shortcut focuses the search input, let’s put the code for it on that + search input, satisfying + locality.

+
+
+

Here is the original HTML for the search input:

+
+
+
+
<input id="search" name="q" type="search" placeholder="Search Contacts">
+
+
+
+

We will add an event listener using the on keydown syntax, which will fire + whenever a keydown occurs. Further, we + can use an event filter syntax in _hyperscript using square brackets after the + event. In the square brackets we + can place a filter expression that will filter out keydown events we + aren’t interested in. In our case, we only + want to consider events where the shift key is held down and where the “S” key + is being pressed. We can create a + boolean expression that inspects the shiftKey property (to see if it is + true) and the code property (to see if + it is "KeyS") of the event to achieve this.

+
+
+

So far our _hyperscript looks like this:

+
+
+
A Start On Our Keyboard Shortcut
+
+
  on keydown[shiftKey and code is 'KeyS'] ...
+
+
+
+

Now, by default, _hyperscript will listen for a given event on the element it is declared + on. So, in this case, with + the script we have so far, we would only get keydown events if the search box + is already focused. That’s not what + we want! We want to have this key work globally, no matter which element has focus. +

+
+
+

Not a problem! We can listen for the keyDown event elsewhere by using a + from clause in our event handler. In this + case we want to listen for the keyDown from the window, and our code ends up + looking, naturally, like this:

+
+
+
Listening Globally
+
+
  on keydown[shiftKey and code is 'KeyS'] from window ...
+
+
+
+

Using the from clause, we can attach the listener to the window while, at the + same time, keeping the code on the + element it logically relates to.

+
+
+

Now that we’ve picked out the event we want to use to focus the search box, let’s + implement the actual focusing by + calling the standard .focus() method.

+
+
+

Here is the entire script, embedded in HTML

+
+
+
Our Final Script
+
+
<input id="search" name="q" type="search" placeholder="Search Contacts"
   _="on keydown[shiftKey and code is 'KeyS'] from the window
        me.focus()"> (1)
-
-
-
-
    -
  1. -

    “me” refers to the element that the script is written on.

    -
  2. -
-
-
-

Given all the functionality, this is surprisingly terse, and, as an English-like programming language, pretty easy to -read.

-
-
-
-

Why a new programming language?

-
-

This is all well and good, but you may be thinking “An entirely new scripting language? That seems excessive.” And, -at some level, you are right: JavaScript is a decent scripting language, is very well optimized and is widely understood -in web development. On the other hand, by creating an entirely new front end scripting language, we had the freedom -to address some problems that we saw generating ugly and verbose code in JavaScript:

-
-
-
-
Async transparency
-
-

In _hyperscript, asynchronous functions (i.e. functions that return Promise instances) can be -invoked as if they were synchronous. Changing a function from sync to async does not break any _hyperscript code that -calls it. This is achieved by checking for a Promise when evaluating any expression, and suspending the running script -if one exists (only the current event handler is suspended and the main thread is not blocked). JavaScript, instead, requires -either the explicit use of callbacks or the use of explicit async annotations (which can’t be mixed with synchronous -code).

-
-
Array property access
-
-

In _hyperscript, accessing a property on an array (other than length or a number) will return -an array of the values of property on each member of that array, making array property access act like a flat-map operation. -jQuery has a similar feature, but only for its own data structure.

-
-
Native CSS Syntax
-
-

In _hyperscript, you can use things like CSS class and ID literals, or CSS query literals, directly -in the language, rather than needing to call out to a wordy DOM API, as you do in JavaScript.

-
-
Deep Event Support
-
-

Working with events in _hyperscript is far more pleasant than working with them in JavaScript, with -native support for responding to and sending events, as well as for common event-handling patterns such as “debouncing” -or rate limiting events. _hyperscript also provides declarative mechanisms for synchronizing events within a given element -and across multiple elements.

-
-
-
-
-

Again we wish to stress that, in this example, we are not stepping outside the lines of a Hypermedia-Driven -Application: we are only adding front-end, client-side functionality with our scripting. We are not creating and -managing a large amount of state outside of the DOM itself, or communicating with the server in a non-hypermedia -exchange.

-
-
-

Additionally, since _hyperscript embeds so well in HTML, it keeps the focus on the hypermedia, rather than on the -scripting logic.

-
-
-

Taken all together, given a certain style of scripting and certain scripting needs, _hyperscript can provide an -excellent scripting experience for your Hypermedia Driven Application. Of course, it is a small and obscure programming -language, so we won’t blame you if you decide to pass on it, but it is at least worth a look to understand what it -is trying to achieve, if only out of intellectual interest.

-
-
-
-
-

10.7. Using Off-the-shelf Components

-
-

That concludes our look at three different options for your scripting infrastructure, that is, the code that you write -to enhance your Hypermedia Driven Application. However, there is another major area to consider when discussing client -side scripting: “off the shelf” components. That is, JavaScript libraries that other people have created that offer -some sort of functionality, such as showing modal dialogs.

-
-
-

Components have become very popular in the web development works, with libraries like DataTables -providing rich user experiences with very little JavaScript code on the part of a user. Unfortunately, if these libraries -aren’t integrated well into a website, they can begin to make an application feel “patched together”. Furthermore, some -libraries go beyond simple DOM manipulation, and require that you integrate with a server end point, almost invariably -with a JSON data API. This means you are no longer building a Hypermedia Driven Application, simply because a particular -widget demands something different. A shame!

-
-
-

Integration Options

-
-

The best JavaScript libraries to work with when you are building a Hypermedia Driven Application are ones that:

-
-
-
    -
  • -

    Mutate the DOM but don’t communicate with a server over JSON

    -
  • -
  • -

    Respect HTML norms (e.g. using input elements to store values)

    -
  • -
  • -

    Trigger many custom events, as the library updates things

    -
  • -
-
-
-

The last point, triggering many custom events (over the alternative of using lots of methods and callbacks) is especially -important, as these custom events can be dispatched or listened to without additional glue code written in a scripting language.

-
-
-

Let’s take a look at two different approaches to scripting, one using JavaScript call backs, and one using events.

-
-
-

To make things concrete, let’s implement a better confirmation dialog for the DELETE button we created in Alpine in the -previous section. In the original example we used the confirm() function built in to JavaScript, which shows a -pretty bare-bones system confirmation dialog. We will replace this function with a popular JavaScript library, -SweetAlert2, that shows a much nicer looking confirmation dialog. Unlink the confirm() function, which blocks -and returns a boolean (true if the user confirmed, false otherwise), SweetAlert2 returns a Promise object, which -is a JavaScript mechanism for hooking in a callback once an asynchronous action (such as waiting for a user to confirm -or deny an action) completes.

-
-
-
Integrating Using Callbacks
-
-

With SweetAlert2 installed as a library, you have access to the Swal object, which has a fire() function on it to -trigger showing an alert. You can pass in arguments to the fire() method to configure exactly what the buttons -on the confirmation dialog look like, what the title of the dialog is, and so forth. We won’t get into these details -too much, but you will see what a dialog looks like in a bit.

-
-
-

So, given we have installed the SweetAlert2 library, we can swap it in place of the confirm() function call. We then -need to restructure the code to pass a callback to the then() method on the Promise that Swal.fire() returns. A -deep dive into Promises is beyond the scope of this chapter, but suffice to say that this callback will be called when -a user confirms or denies the action. If the user confirmed the action, then the result.isConfirmed property will be -true.

-
-
-

Given all that, our updated code will look like this:

-
-
-
A Callback-based Confirmation Dialog
-
-
<button type="button" class="bad bg color border"
+                        
+
+
+
    +
  1. +

    “me” refers to the element that the script is written on.

    +
  2. +
+
+
+

Given all the functionality, this is surprisingly terse, and, as an English-like programming + language, pretty easy to + read.

+
+
+
+

Why a new programming language?

+
+

This is all well and good, but you may be thinking “An entirely new scripting language? + That seems excessive.” And, + at some level, you are right: JavaScript is a decent scripting language, is very well + optimized and is widely understood + in web development. On the other hand, by creating an entirely new front end scripting + language, we had the freedom + to address some problems that we saw generating ugly and verbose code in JavaScript:

+
+
+
+
Async transparency
+
+

In _hyperscript, asynchronous functions (i.e. functions that return + Promise instances) can be + invoked as if they were synchronous. Changing a function from sync to async + does not break any _hyperscript code that + calls it. This is achieved by checking for a Promise when evaluating any expression, + and suspending the running script + if one exists (only the current event handler is suspended and the main thread is + not blocked). JavaScript, instead, requires + either the explicit use of callbacks or the use of explicit + async annotations (which can’t be mixed with synchronous + code).

+
+
Array property access
+
+

In _hyperscript, accessing a property on an array (other than length or + a number) will return + an array of the values of property on each member of that array, making array + property access act like a flat-map operation. + jQuery has a similar feature, but only for its own data structure.

+
+
Native CSS Syntax
+
+

In _hyperscript, you can use things like CSS class and ID literals, or CSS query + literals, directly + in the language, rather than needing to call out to a wordy DOM API, as you do in + JavaScript.

+
+
Deep Event Support
+
+

Working with events in _hyperscript is far more pleasant than working with them in + JavaScript, with + native support for responding to and sending events, as well as for common + event-handling patterns such as “debouncing” + or rate limiting events. _hyperscript also provides declarative mechanisms for + synchronizing events within a given element + and across multiple elements.

+
+
+
+
+

Again we wish to stress that, in this example, we are not stepping outside the lines of a + Hypermedia-Driven + Application: we are only adding front-end, client-side functionality with our scripting. We + are not creating and + managing a large amount of state outside of the DOM itself, or communicating with the server + in a non-hypermedia + exchange.

+
+
+

Additionally, since _hyperscript embeds so well in HTML, it keeps the focus on the + hypermedia, rather than on the + scripting logic.

+
+
+

Taken all together, given a certain style of scripting and certain scripting needs, + _hyperscript can provide an + excellent scripting experience for your Hypermedia Driven Application. Of course, it is a + small and obscure programming + language, so we won’t blame you if you decide to pass on it, but it is at least worth + a look to understand what it + is trying to achieve, if only out of intellectual interest.

+
+
+
+
+

10.7. Using Off-the-shelf Components

+
+

That concludes our look at three different options for your scripting infrastructure, + that is, the code that you write + to enhance your Hypermedia Driven Application. However, there is another major area to consider + when discussing client + side scripting: “off the shelf” components. That is, JavaScript libraries that other + people have created that offer + some sort of functionality, such as showing modal dialogs.

+
+
+

Components have become very popular in the web development works, with libraries like DataTables + providing rich user experiences with very little JavaScript code on the part of a user. + Unfortunately, if these libraries + aren’t integrated well into a website, they can begin to make an application feel + “patched together”. Furthermore, some + libraries go beyond simple DOM manipulation, and require that you integrate with a server end + point, almost invariably + with a JSON data API. This means you are no longer building a Hypermedia Driven Application, + simply because a particular + widget demands something different. A shame!

+
+
+

Integration Options

+
+

The best JavaScript libraries to work with when you are building a Hypermedia Driven + Application are ones that:

+
+
+
    +
  • +

    Mutate the DOM but don’t communicate with a server over JSON

    +
  • +
  • +

    Respect HTML norms (e.g. using input elements to store values)

    +
  • +
  • +

    Trigger many custom events, as the library updates things

    +
  • +
+
+
+

The last point, triggering many custom events (over the alternative of using lots of methods + and callbacks) is especially + important, as these custom events can be dispatched or listened to without additional glue + code written in a scripting language.

+
+
+

Let’s take a look at two different approaches to scripting, one using JavaScript call + backs, and one using events.

+
+
+

To make things concrete, let’s implement a better confirmation dialog for the + DELETE button we created in Alpine in the + previous section. In the original example we used the confirm() function built + in to JavaScript, which shows a + pretty bare-bones system confirmation dialog. We will replace this function with a popular + JavaScript library, + SweetAlert2, that shows a much nicer looking confirmation dialog. Unlink the + confirm() function, which blocks + and returns a boolean (true if the user confirmed, false + otherwise), SweetAlert2 returns a Promise object, which + is a JavaScript mechanism for hooking in a callback once an asynchronous action (such as + waiting for a user to confirm + or deny an action) completes.

+
+
+
Integrating Using Callbacks
+
+

With SweetAlert2 installed as a library, you have access to the Swal object, + which has a fire() function on it to + trigger showing an alert. You can pass in arguments to the fire() method to + configure exactly what the buttons + on the confirmation dialog look like, what the title of the dialog is, and so forth. We + won’t get into these details + too much, but you will see what a dialog looks like in a bit.

+
+
+

So, given we have installed the SweetAlert2 library, we can swap it in place of the + confirm() function call. We then + need to restructure the code to pass a callback to the then() + method on the Promise that Swal.fire() returns. A + deep dive into Promises is beyond the scope of this chapter, but suffice to say that + this callback will be called when + a user confirms or denies the action. If the user confirmed the action, then the + result.isConfirmed property will be + true. +

+
+
+

Given all that, our updated code will look like this:

+
+
+
A Callback-based Confirmation Dialog
+
+
<button type="button" class="bad bg color border"
   @click="Swal.fire({  (1)
                   title: 'Delete these contacts?',  (2)
                   showCancelButton: true,
@@ -12354,55 +18076,65 @@ a user confirms or denies the action.  If the user confirmed the action, then th
                   }
                });"
 >Delete</button>
-
-
-
-
    -
  1. -

    Invoke the Swal.fire() function

    -
  2. -
  3. -

    Configure how the dialog appears

    -
  4. -
  5. -

    Handle the result of the users selection

    -
  6. -
-
-
-

And now, when this button is clicked, we get a nice looking dialog in our web application:

-
-
-
-screenshot sweet alert -
-
-
-

Much nicer than the system confirmation dialog. Still, this feels a little wrong. This is a lot of code to write -just to trigger a slightly nicer confirm(), isn’t it? And the htmx JavaScript code we are using here feels a little -awkward. It would be more natural to move the htmx out to attributes on the button, as we have been doing, and then -trigger the request via events.

-
-
-

So let’s take a different approach and see how that looks.

-
-
-
-
Integrating Using Events
-
-

To clean this code up, we will pull the Swal.fire() code out to a custom JavaScript function we will create called -sweetConfirm(). sweetConfirm() will take the dialog options that are passed into the fire() method, as well as -the element that is confirming an action. The big difference between the code we already have and sweetConfirm() is -that sweetConfirm(), rather than calling some htmx directly, will, instead, trigger a confirmed event on the -button when the user confirms they wish to delete.

-
-
-

Here is what our JavaScript function looks like:

-
-
-
An Event-based Confirmation Dialog
-
-
function sweetConfirm(elt, config) {
+                            
+
+
+
    +
  1. +

    Invoke the Swal.fire() function

    +
  2. +
  3. +

    Configure how the dialog appears

    +
  4. +
  5. +

    Handle the result of the users selection

    +
  6. +
+
+
+

And now, when this button is clicked, we get a nice looking dialog in our web + application:

+
+
+
+ screenshot sweet alert +
+
+
+

Much nicer than the system confirmation dialog. Still, this feels a little wrong. This is + a lot of code to write + just to trigger a slightly nicer confirm(), isn’t it? And the htmx + JavaScript code we are using here feels a little + awkward. It would be more natural to move the htmx out to attributes on the button, as + we have been doing, and then + trigger the request via events.

+
+
+

So let’s take a different approach and see how that looks.

+
+
+
+
Integrating Using Events
+
+

To clean this code up, we will pull the Swal.fire() code out to a custom + JavaScript function we will create called + sweetConfirm(). sweetConfirm() will take the dialog options + that are passed into the fire() method, as well as + the element that is confirming an action. The big difference between the code we already + have and sweetConfirm() is + that sweetConfirm(), rather than calling some htmx directly, will, instead, + trigger a confirmed event on the + button when the user confirms they wish to delete. +

+
+
+

Here is what our JavaScript function looks like:

+
+
+
An Event-based Confirmation Dialog
+
+
function sweetConfirm(elt, config) {
       Swal.fire(config) (1)
           .then((result) => {
                   if (result.isConfirmed) {
@@ -12410,493 +18142,678 @@ button when the user confirms they wish to delete.

} }); }
-
-
-
-
    -
  1. -

    Pass the config through to the fire() function

    -
  2. -
  3. -

    If the user confirmed the action, trigger a confirmed event

    -
  4. -
-
-
-

With this method available, we can now tighten up our delete button quite a bit. We can remove all the SweetAlert2 -code that we had in the @click Alpine attribute, and simply call this new sweetConfirm() method, passing in the -arguments $el, which is the Alpine syntax for getting `"the current element`" that the script is on, and then -the exact configuration we want for our dialog.

-
-
-

If the user confirms the action, a confirmed event will be triggered on the button. This means that we can go back -to using our trusty htmx attributes! Namely, we can move DELETE to an hx-delete attribute, and we can we can use -hx-target to target the body. And then, and here is the crucial step, we can use the confirmed event that is -triggered in the sweetConfirm() function, to trigger the request, but adding an hx-trigger for it.

-
-
-

Here is what our code looks like:

-
-
-
An Event-based Confirmation Dialog
-
-
<button type="button" class="bad bg color border"
+                            
+
+
+
    +
  1. +

    Pass the config through to the fire() function

    +
  2. +
  3. +

    If the user confirmed the action, trigger a confirmed event

    +
  4. +
+
+
+

With this method available, we can now tighten up our delete button quite a bit. We can + remove all the SweetAlert2 + code that we had in the @click Alpine attribute, and simply call this new + sweetConfirm() method, passing in the + arguments $el, which is the Alpine syntax for getting `"the current + element`" that the script is on, and then + the exact configuration we want for our dialog.

+
+
+

If the user confirms the action, a confirmed event will be triggered on the + button. This means that we can go back + to using our trusty htmx attributes! Namely, we can move DELETE to an + hx-delete attribute, and we can we can use + hx-target to target the body. And then, and here is the crucial step, we + can use the confirmed event that is + triggered in the sweetConfirm() function, to trigger the request, but + adding an hx-trigger for it. +

+
+
+

Here is what our code looks like:

+
+
+
An Event-based Confirmation Dialog
+
+
<button type="button" class="bad bg color border"
         hx-delete="/contacts" hx-target="body" hx-trigger="confirmed" (1)
         @click="sweetConfirm($el, (2)
                 { title: 'Delete these contacts?',  (3)
                   showCancelButton: true,
                   confirmButtonText: 'Delete'})">
-
-
-
-
    -
  1. -

    htmx attributes are back

    -
  2. -
  3. -

    We pass the button in to the function, so an event can be triggered on it

    -
  4. -
  5. -

    We pass through the SweetAlert2 configuration information

    -
  6. -
-
-
-

As you can see, this event-based code is much cleaner and certainly more “HTML-ish”. The key to this cleaner -implementation is that our new sweetConfirm() function fires an event that htmx is able to listen for.

-
-
-

This is why a rich event model is important to look for when choosing a library to work with, both with htmx and with -Hypermedia-Driven Applications in general.

-
-
-

Unfortunately, due to the prevalence and dominance of the JavaScript-first mindset today, many libraries are like -SweetAlert2: they expect you to pass a callback in the first style. In these cases you can use the technique we -have demonstrated here, wrapping the library in a function that triggers events in a callback, to make the library more -hypermedia and htmx-friendly.

-
-
-
-
-
-

10.8. Pragmatic Scripting

-
-
-
-

In case of conflict, consider users over authors over implementors over specifiers over theoretical purity.

-
-
-
-— W3C
-HTML Design Principles § 3.2 Priority of Constituencies -
-
-
-

We have shown you quite a few tools and techniques for scripting in a Hypermedia Driven Application. How should you -pick between them? The sad truth is that there will never be a single, always correct answer to this question.

-
-
-

Are you committed to vanilla JavaScript-only, perhaps due to company policy? Well, you can use vanilla JavaScript effectively -to script your Hypermedia-Driven Application.

-
-
-

Do you have more leeway and like the look of Alpine.js? That’s a great way to add more structured, localized JavaScript -to your application, and offers some nice reactive features as well.

-
-
-

Are you a bit more bold in your technical choices? Maybe _hyperscript would be worth taking a look at. (We certainly think so.)

-
-
-

Sometimes you might even consider picking two (or more) of these approaches within an application. Each has its own -strengths and weaknesses, and all of them are relatively small and self-contained, so picking the right tool for the job -at hand might be the best approach.

-
-
-

In general, we encourage a pragmatic approach to scripting: whatever feels right is probably right (or, at least, -right enough) for you. Rather than being concerned about which particular approach is taken for your scripting, -we would focus with these more general concerns:

-
-
-
    -
  • -

    Avoiding communicating with the server via JSON data APIs

    -
  • -
  • -

    Avoiding storing large amounts of state outside of the DOM

    -
  • -
  • -

    Favoring using events, rather than hard-coded callbacks or method calls

    -
  • -
-
-
-

But even on these topics, sometimes a web developer has to do what a web developer has to do. If the perfect widget -for your application exists but, darn it, it uses a JSON data API, that’s OK.

-
-
-

Just don’t make it a habit.

-
-
-
-
-
-

11. JSON Data APIs & Hypermedia Driven Applications

-
-
-

JSON Data APIs

-
-

So far we have been focusing on using hypermedia to build Hypermedia-Driven Applications. In doing so we are -following the original networking architecture of the web, and building a RESTful system, in the original sense -of that term.

-
-
-

However, today, we should acknowledge that many web applications are often not built using this approach. Instead, they use a -Single Page Application front end library such as React to build their application, and they interact with the server -via a JSON API. This JSON API almost never uses hypermedia concepts. Rather JSON APIs tend to be Data APIs, that -is, an API that simply returns structured domain data to the client without any hypermedia control information. The client -itself must know how to interpret the JSON Data: what end points are associated with the data, how certain fields should -be interpreted, and so on.

-
-
-

Now, believe it or not, we have been creating an API for Contact.app.

-
-
-

This may sound confusing to you: an API? We have just been creating a web application, with handlers that just return -HTML.

-
-
-

How is that an API?

-
-
-

It turns out that Contact.app is, indeed, providing an API. It just happens to be a hypermedia API that a hypermedia client, -that is, a browser, understands. We are building an API for the browser to interact with over HTTP, and, thanks to the -magic of HTML and hypermedia, the browser doesn’t need to know anything about our hypermedia API beyond an entry point -URL: all the actions and display information comes, self-contained, within the HTML responses.

-
-
-

Building RESTful web applications like this is so natural and simple that you might not think of it as an API at all, but -we assure you, it is.

-
-
-
-

11.1. Hypermedia APIs & JSON Data APIs

-
-

So, we have a hypermedia API for Contact.app. Should we include a Data API for Contact.app as well?

-
-
-

Sure! The existence of a hypermedia API in no way means that you can’t also have a Data API. In fact, this is a -common situation in traditional web applications: there is the “web application” that is entered through that entry point -URL, say https://mywebapp.example.com/. And there is also a separate JSON API that is accessible through another -URL, perhaps https://api.mywebapp.example.com/v1.

-
-
-

This is a perfectly reasonable way to split up the hypermedia interface to your application and the Data API you provide -to other, non-hypermedia clients.

-
-
-

Why would you want to include a Data API along with a hypermedia API? Well, because non-hypermedia clients might also -want to interact with your application as well.

-
-
-

For example:

-
-
-
    -
  • -

    Perhaps you have a mobile application that isn’t built using Hyperview. That application will need to interact with -your server somehow, and using the existing HTML API would almost certainly be a poor fit! You want programmatic -access to your system via a Data API, and JSON is a natural choice for this.

    -
  • -
  • -

    Perhaps you have an automated script that needs to interact with the system on a regular basis. For example, maybe we -have a bulk-import job that runs nightly, and needs to import/sync thousands of contacts. While it would be possible -to script this against the HTML API, it would also be annoying: parsing HTML in scripts is error prone and tedious. It -would be better to have a simple JSON API for this use case.

    -
  • -
  • -

    Perhaps there are 3rd party clients who wish to integrate with your system’s data in some way. Maybe a partner -wants to synchronize data nightly. As with the bulk-import example, this isn’t a great use case for an HTML-based API, -and it would make more sense to provide something more amenable to scripting.

    -
  • -
-
-
-

For all of these use cases, a JSON Data API makes sense: in each case the API is not being consumed by a hypermedia client, -so presenting an HTML-based hypermedia API would be inefficient and complicated for the client to deal with. A simple JSON Data API fits the bill for what we want and, as always, we recommend using the right tool for the job.

-
- -
-

Differences Between Hypermedia APIs & Data APIs

-
-

Let’s accept for a moment that we are going to have a Data API for our application, in addition to our hypermedia API. -At this point, some developers may be wondering: why have both? Why not have a single API, the JSON Data API, and have -multiple clients use this one API to communicate with it?

-
-
-

Isn’t it redundant to have both types of APIs for our application?

-
-
-

This is a reasonable point: we do advocate having multiple APIs to your web application if necessary and, yes, this may -lead to some redundancy in code. However, there are distinct advantages to both sorts of APIs and, even more so, -distinct requirements for both sorts of APIs.

-
-
-

By supporting both of these types of APIs separately you can get the strengths of both, while keeping their varying -styles of code and infrastructure needs cleanly split out.

-
-
-

Let’s contrast the needs of JSON APIs with Hypermedia APIs:

-
- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
JSON API NeedsHypermedia API
-

It must remain stable over time: you cannot change the API willy nilly or you risk breaking clients that use the API -and expect certain end points to behave in certain ways

-
-

There is no need to remain stable over time: all URLs are discovered via HTML responses, so you can be much more aggressive in changing the shape of a hypermedia API

-
-

It must be versioned: related to the first point, when you do make a major change, you need to version the API so that clients that are using the old API continue to work

-
-

This means that versioning is not an issue, another strength of the hypermedia approach

-
-

It should be rate limited: since data APIs are often used by other clients, not just your own internal web application, requests should be rate limited, often by user, in order to avoid a single client overloading the system

-
-

Rate limiting probably isn’t as important beyond the prevention of Distributed Denial of Service (DDoS) attacks

-
-

It should be a general API: since the API is for all clients, not just for your web application, you should avoid specialized end points that are driven by your own application needs. Instead, the API should be general and expressive enough to satisfy as many potential client needs as possible.

-
-

The API can be very specific to your application needs: since it is designed only for your particular web application, and since the API is discovered through hypermedia, you can add and remove highly tuned end points for specific features or optimization needs in your application

-
-

Authentication for these sorts of API is typically token based, which we will discuss in more detail later

-
-

Authentication is typically managed through a session cookie established by a login page

-
-
-

These two different types of APIs have different strengths and needs, so it makes sense to use both. The hypermedia -approach can be used for your web application, allowing you to specialize the API for the “shape” -of your application. The Data API approach can be used for other, non-hypermedia clients like mobile, integration -partners, etc.

-
-
-

Note in particular that, by splitting these two APIs apart from one another, you reduce the pressure that running your web -application through your general Data API produces to be constantly changing the API to address application needs. Rather -than being thrashed around with every feature change, your Data API can focus on remaining stable and reliable.

-
-
-

This is the core strength of splitting your Data API from your Hypermedia API, in our opinion.

-
- -
-
-
-

11.2. Adding a JSON Data API To Contact.app

-
-

Alright, so how are we going to add a JSON Data API to our application? One approach, popularized by the Ruby on Rails -web framework, is to use the same URL endpoints as your hypermedia application, but use the HTTP Accept header to -determine if the client wants a JSON representation or an HTML representation. The HTTP Accept header allows a client -to specify what sort of Multipurpose Internet Mail Extensions (MIME) types, that is file types, it wants back from the -server: JSON, HTML, text and so on.

-
-
-

So, if the client wanted a JSON representation of all contacts, they might issue a GET request that looks like this:

-
-
-
A Request for a JSON Representation of All Contacts
-
-
Accept: application/json
+                            
+
+
+
    +
  1. +

    htmx attributes are back

    +
  2. +
  3. +

    We pass the button in to the function, so an event can be triggered on it

    +
  4. +
  5. +

    We pass through the SweetAlert2 configuration information

    +
  6. +
+
+
+

As you can see, this event-based code is much cleaner and certainly more + “HTML-ish”. The key to this cleaner + implementation is that our new sweetConfirm() function fires an event that + htmx is able to listen for.

+
+
+

This is why a rich event model is important to look for when choosing a library to work + with, both with htmx and with + Hypermedia-Driven Applications in general.

+
+
+

Unfortunately, due to the prevalence and dominance of the JavaScript-first mindset today, + many libraries are like + SweetAlert2: they expect you to pass a callback in the first style. In these cases you + can use the technique we + have demonstrated here, wrapping the library in a function that triggers events in a + callback, to make the library more + hypermedia and htmx-friendly.

+
+
+
+
+
+

10.8. Pragmatic Scripting

+
+
+
+

In case of conflict, consider users over authors over implementors over specifiers over + theoretical purity.

+
+
+
+ — W3C
+ HTML Design Principles § 3.2 Priority of Constituencies +
+
+
+

We have shown you quite a few tools and techniques for scripting in a Hypermedia Driven + Application. How should you + pick between them? The sad truth is that there will never be a single, always correct answer to + this question.

+
+
+

Are you committed to vanilla JavaScript-only, perhaps due to company policy? Well, you can use + vanilla JavaScript effectively + to script your Hypermedia-Driven Application.

+
+
+

Do you have more leeway and like the look of Alpine.js? That’s a great way to add more + structured, localized JavaScript + to your application, and offers some nice reactive features as well.

+
+
+

Are you a bit more bold in your technical choices? Maybe _hyperscript would be worth taking a + look at. (We certainly think so.)

+
+
+

Sometimes you might even consider picking two (or more) of these approaches within an + application. Each has its own + strengths and weaknesses, and all of them are relatively small and self-contained, so picking + the right tool for the job + at hand might be the best approach.

+
+
+

In general, we encourage a pragmatic approach to scripting: whatever feels right is + probably right (or, at least, + right enough) for you. Rather than being concerned about which particular approach is + taken for your scripting, + we would focus with these more general concerns:

+
+
+
    +
  • +

    Avoiding communicating with the server via JSON data APIs

    +
  • +
  • +

    Avoiding storing large amounts of state outside of the DOM

    +
  • +
  • +

    Favoring using events, rather than hard-coded callbacks or method calls

    +
  • +
+
+
+

But even on these topics, sometimes a web developer has to do what a web developer has to do. If + the perfect widget + for your application exists but, darn it, it uses a JSON data API, that’s OK.

+
+
+

Just don’t make it a habit.

+
+
+
+
+
+

11. JSON Data APIs & Hypermedia Driven Applications +

+
+
+

JSON Data APIs

+
+

So far we have been focusing on using hypermedia to build Hypermedia-Driven Applications. In + doing so we are + following the original networking architecture of the web, and building a RESTful system, in the + original sense + of that term.

+
+
+

However, today, we should acknowledge that many web applications are often not built + using this approach. Instead, they use a + Single Page Application front end library such as React to build their application, and they + interact with the server + via a JSON API. This JSON API almost never uses hypermedia concepts. Rather JSON APIs tend to be + Data APIs, that + is, an API that simply returns structured domain data to the client without any hypermedia + control information. The client + itself must know how to interpret the JSON Data: what end points are associated with the data, + how certain fields should + be interpreted, and so on.

+
+
+

Now, believe it or not, we have been creating an API for Contact.app.

+
+
+

This may sound confusing to you: an API? We have just been creating a web application, with + handlers that just return + HTML.

+
+
+

How is that an API?

+
+
+

It turns out that Contact.app is, indeed, providing an API. It just happens to be a + hypermedia API that a hypermedia client, + that is, a browser, understands. We are building an API for the browser to interact with over + HTTP, and, thanks to the + magic of HTML and hypermedia, the browser doesn’t need to know anything about our + hypermedia API beyond an entry point + URL: all the actions and display information comes, self-contained, within the HTML responses. +

+
+
+

Building RESTful web applications like this is so natural and simple that you might not think of + it as an API at all, but + we assure you, it is.

+
+
+
+

11.1. Hypermedia APIs & JSON Data APIs

+
+

So, we have a hypermedia API for Contact.app. Should we include a Data API for Contact.app as + well?

+
+
+

Sure! The existence of a hypermedia API in no way means that you can’t + also have a Data API. In fact, this is a + common situation in traditional web applications: there is the “web application” + that is entered through that entry point + URL, say + https://mywebapp.example.com/. + And there is also a separate JSON API that is accessible through another + URL, perhaps + https://api.mywebapp.example.com/v1. +

+
+
+

This is a perfectly reasonable way to split up the hypermedia interface to your application and + the Data API you provide + to other, non-hypermedia clients.

+
+
+

Why would you want to include a Data API along with a hypermedia API? Well, because + non-hypermedia clients might also + want to interact with your application as well.

+
+
+

For example:

+
+
+
    +
  • +

    Perhaps you have a mobile application that isn’t built using Hyperview. That + application will need to interact with + your server somehow, and using the existing HTML API would almost certainly be a poor + fit! You want programmatic + access to your system via a Data API, and JSON is a natural choice for this.

    +
  • +
  • +

    Perhaps you have an automated script that needs to interact with the system on a regular + basis. For example, maybe we + have a bulk-import job that runs nightly, and needs to import/sync thousands of + contacts. While it would be possible + to script this against the HTML API, it would also be annoying: parsing HTML in scripts + is error prone and tedious. It + would be better to have a simple JSON API for this use case.

    +
  • +
  • +

    Perhaps there are 3rd party clients who wish to integrate with your system’s data + in some way. Maybe a partner + wants to synchronize data nightly. As with the bulk-import example, this isn’t a + great use case for an HTML-based API, + and it would make more sense to provide something more amenable to scripting.

    +
  • +
+
+
+

For all of these use cases, a JSON Data API makes sense: in each case the API is not being + consumed by a hypermedia client, + so presenting an HTML-based hypermedia API would be inefficient and complicated for the client + to deal with. A simple JSON Data API fits the bill for what we want and, as always, we recommend + using the right tool for the job.

+
+ +
+

Differences Between Hypermedia APIs & + Data APIs

+
+

Let’s accept for a moment that we are going to have a Data API for our + application, in addition to our hypermedia API. + At this point, some developers may be wondering: why have both? Why not have a single API, + the JSON Data API, and have + multiple clients use this one API to communicate with it?

+
+
+

Isn’t it redundant to have both types of APIs for our application?

+
+
+

This is a reasonable point: we do advocate having multiple APIs to your web application if + necessary and, yes, this may + lead to some redundancy in code. However, there are distinct advantages to both sorts of + APIs and, even more so, + distinct requirements for both sorts of APIs.

+
+
+

By supporting both of these types of APIs separately you can get the strengths of both, while + keeping their varying + styles of code and infrastructure needs cleanly split out.

+
+
+

Let’s contrast the needs of JSON APIs with Hypermedia APIs:

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
JSON API NeedsHypermedia API
+
+
+

It must remain stable over time: you cannot change the API willy nilly or + you risk breaking clients that use the API + and expect certain end points to behave in certain ways

+
+
+
+
+
+

There is no need to remain stable over time: all URLs are discovered via + HTML responses, so you can be much more aggressive in changing the shape + of a hypermedia API

+
+
+
+
+
+

It must be versioned: related to the first point, when you do make a + major change, you need to version the API so that clients that are using + the old API continue to work

+
+
+
+
+
+

This means that versioning is not an issue, another strength of the + hypermedia approach

+
+
+
+
+
+

It should be rate limited: since data APIs are often used by other + clients, not just your own internal web application, requests should be + rate limited, often by user, in order to avoid a single client + overloading the system

+
+
+
+
+
+

Rate limiting probably isn’t as important beyond the prevention of + Distributed Denial of Service (DDoS) attacks

+
+
+
+
+
+

It should be a general API: since the API is for all clients, + not just for your web application, you should avoid specialized end + points that are driven by your own application needs. Instead, the API + should be general and expressive enough to satisfy as many potential + client needs as possible.

+
+
+
+
+
+

The API can be very specific to your application needs: since it + is designed only for your particular web application, and since the API + is discovered through hypermedia, you can add and remove highly tuned + end points for specific features or optimization needs in your + application

+
+
+
+
+
+

Authentication for these sorts of API is typically token based, which we + will discuss in more detail later

+
+
+
+
+
+

Authentication is typically managed through a session cookie established + by a login page

+
+
+
+
+

These two different types of APIs have different strengths and needs, so it makes sense to + use both. The hypermedia + approach can be used for your web application, allowing you to specialize the API for the + “shape” + of your application. The Data API approach can be used for other, non-hypermedia clients + like mobile, integration + partners, etc.

+
+
+

Note in particular that, by splitting these two APIs apart from one another, you reduce the + pressure that running your web + application through your general Data API produces to be constantly changing the API to + address application needs. Rather + than being thrashed around with every feature change, your Data API can focus on remaining + stable and reliable.

+
+
+

This is the core strength of splitting your Data API from your Hypermedia API, in our + opinion.

+
+ +
+
+
+

11.2. Adding a JSON Data API To Contact.app

+
+

Alright, so how are we going to add a JSON Data API to our application? One approach, popularized + by the Ruby on Rails + web framework, is to use the same URL endpoints as your hypermedia application, but use the HTTP + Accept header to + determine if the client wants a JSON representation or an HTML representation. The HTTP + Accept header allows a client + to specify what sort of Multipurpose Internet Mail Extensions (MIME) types, that is file types, + it wants back from the + server: JSON, HTML, text and so on.

+
+
+

So, if the client wanted a JSON representation of all contacts, they might issue a + GET request that looks like this:

+
+
+
A Request for a JSON Representation of All Contacts
+
+
Accept: application/json
 
 GET /contacts
-
-
-
-

If we adopted this pattern then our request handler for /contacts/ would need to be updated to inspect this header and, -depending on the value, return a JSON rather than HTML representation for the contacts. Ruby on Rails has support for -this pattern baked into the framework, making it very easy to switch on the requested MIME type.

-
-
-

Unfortunately, our experience with this pattern has not been great, for reasons that should be clear given the -differences we outlined between Data and hypermedia APIs: they have different needs and often take on very different -“shapes”, and trying to pound them into the same set of URLs ends up creating a lot of tension in the application code.

-
-
-

Given the different needs of the two APIs and our experience managing multiple APIs like this, we think separating the two - from one another, and, therefore, breaking the JSON Data API out to its own set of URLs is the right choice. This will -allow us to evolve the two APIs separately from one another, and give us room to improve each independently, in a manner -consistent with their own individual strengths.

-
-
-

Picking a Root URL For Our API

-
-

Given that we are going to split our JSON Data API routes out from our regular hypermedia routes, where should we place -them? One important consideration here is that we want to make sure that we can version our API cleanly in some way, -regardless of the pattern we choose.

-
-
-

Looking around, a lot of places use a subdomain for their APIs, something -like https://api.mywebapp.example.com and, in fact, often encode versioning in the subdomain: https://v1.api.mywebapp.example.com.

-
-
-

While this makes sense for large companies, it seems like a bit of overkill for our modest little Contact.app. Rather -than using subdomains, which are a pain for local development, we will use sub-paths within the existing application:

-
-
-
    -
  • -

    We will use /api as the root for our Data API functionality

    -
  • -
  • -

    We will use /api/v1 as the entry point for version 1 of our Data API

    -
  • -
-
-
-

If and when we decide to bump the API version, we can move to /api/v2 and so on.

-
-
-

This approach isn’t perfect, of course, but it will work for our simple application and can be adapted to a subdomain -approach or various other methods at a later point, when our Contact.app has taken over the internet and we can afford -a large team of API developers. :)

-
-
-
-

Our First JSON Endpoint: Listing All Contacts

-
-

Let’s add our first Data API end point. It will handle an HTTP GET request to /api/v1/contacts, and return -a JSON list of all contacts in the system. In some ways it will look quite a bit like our initial code for the -hypermedia route /contacts: we will load all the contacts from the contacts database and then render some text -as a response.

-
-
-

We are also going to take advantage of a nice feature of Flask: if you simply return an object from a handler, it will -serialized (that is, convert) that object into a JSON response. This makes it very easy to build simple JSON APIs -in flask!

-
-
-

Here is our code:

-
-
-
A JSON Data API To Return All Contacts
-
-
@app.route("/api/v1/contacts", methods=["GET"]) (1)
+                    
+
+
+

If we adopted this pattern then our request handler for /contacts/ would need to be + updated to inspect this header and, + depending on the value, return a JSON rather than HTML representation for the contacts. Ruby on + Rails has support for + this pattern baked into the framework, making it very easy to switch on the requested MIME type. +

+
+
+

Unfortunately, our experience with this pattern has not been great, for reasons that should be + clear given the + differences we outlined between Data and hypermedia APIs: they have different needs and often + take on very different + “shapes”, and trying to pound them into the same set of URLs ends up creating a lot + of tension in the application code.

+
+
+

Given the different needs of the two APIs and our experience managing multiple APIs like this, we + think separating the two + from one another, and, therefore, breaking the JSON Data API out to its own set of URLs is the + right choice. This will + allow us to evolve the two APIs separately from one another, and give us room to improve each + independently, in a manner + consistent with their own individual strengths.

+
+
+

Picking a Root URL For Our API

+
+

Given that we are going to split our JSON Data API routes out from our regular hypermedia + routes, where should we place + them? One important consideration here is that we want to make sure that we can version our + API cleanly in some way, + regardless of the pattern we choose.

+
+
+

Looking around, a lot of places use a subdomain for their APIs, something + like + https://api.mywebapp.example.com + and, in fact, often encode versioning in the subdomain: + https://v1.api.mywebapp.example.com. +

+
+
+

While this makes sense for large companies, it seems like a bit of overkill for our modest + little Contact.app. Rather + than using subdomains, which are a pain for local development, we will use sub-paths within + the existing application:

+
+
+
    +
  • +

    We will use /api as the root for our Data API functionality

    +
  • +
  • +

    We will use /api/v1 as the entry point for version 1 of our Data API

    +
  • +
+
+
+

If and when we decide to bump the API version, we can move to /api/v2 and so on. +

+
+
+

This approach isn’t perfect, of course, but it will work for our simple application and + can be adapted to a subdomain + approach or various other methods at a later point, when our Contact.app has taken over the + internet and we can afford + a large team of API developers. :)

+
+
+
+

Our First JSON Endpoint: Listing All Contacts +

+
+

Let’s add our first Data API end point. It will handle an HTTP GET request + to /api/v1/contacts, and return + a JSON list of all contacts in the system. In some ways it will look quite a bit like our + initial code for the + hypermedia route /contacts: we will load all the contacts from the contacts + database and then render some text + as a response.

+
+
+

We are also going to take advantage of a nice feature of Flask: if you simply return an + object from a handler, it will + serialized (that is, convert) that object into a JSON response. This makes it very easy to + build simple JSON APIs + in flask!

+
+
+

Here is our code:

+
+
+
A JSON Data API To Return All Contacts
+
+
@app.route("/api/v1/contacts", methods=["GET"]) (1)
 def json_contacts():
     contacts_set = Contact.all()
     contacts_dicts = [c.__dict__ for c in contacts_set] (2)
     return {"contacts": contacts_dicts} (3)
-
-
-
-
    -
  1. -

    JSON API gets its own path, starting with /api

    -
  2. -
  3. -

    Convert the contacts array into an array of simple dictionary (map) objects

    -
  4. -
  5. -

    Return a dictionary that contains a contacts property of all the contacts

    -
  6. -
-
-
-

This Python code might look a little foreign to you if you are not a Python developer, but all we are doing is converting -our contacts into an array of simple name/value pairs and returning that array in an enclosing object as the contacts -property. This object will be serialized into a JSON response automatically by Flask.

-
-
-

With this in place, if we make an HTTP GET request to /api/v1/contacts, we will see a response that looks something -like this:

-
-
-
Some Sample Data From Our API
-
-
{
+                        
+
+
+
    +
  1. +

    JSON API gets its own path, starting with /api

    +
  2. +
  3. +

    Convert the contacts array into an array of simple dictionary (map) objects

    +
  4. +
  5. +

    Return a dictionary that contains a contacts property of all the + contacts

    +
  6. +
+
+
+

This Python code might look a little foreign to you if you are not a Python developer, but + all we are doing is converting + our contacts into an array of simple name/value pairs and returning that array in an + enclosing object as the contacts + property. This object will be serialized into a JSON response automatically by Flask.

+
+
+

With this in place, if we make an HTTP GET request to + /api/v1/contacts, we will see a response that looks something + like this:

+
+
+
Some Sample Data From Our API
+
+
{
   "contacts": [
     {
       "email": "carson@example.com",
@@ -12917,62 +18834,75 @@ like this:

... ] }
-
-
-
-

So, you can see, we now have a way to get a relatively simple JSON representation of our contacts via an HTTP request. -Not perfect, but good enough for the purposes of this book! It’s certainly good enough to write some basic automated -scripts against.

-
-
-

For example:

-
-
-
    -
  • -

    You could move your contacts to another system on a nightly basis

    -
  • -
  • -

    You could back your contacts up to a local file

    -
  • -
  • -

    You could automate an email blast to your contacts

    -
  • -
-
-
-

Having this small JSON Data API opens up a lot of automation possibilities that would be messier to achieve with our existing -hypermedia API.

-
-
-
-

Adding Contacts

-
-

Let’s move on the next piece of functionality: adding a new contact to the system. Once again, our code is going -to look similar in some ways to the code that we wrote for our normal web application. However, here we are also -going to see the JSON API and the hypermedia API for our web application begin to obviously diverge.

-
-
-

In the web application, we needed a separate path, /contacts/new to host the HTML form for creating a new contact. In -the web application we made the decision to issue a POST to that same path to keep things consistent.

-
-
-

In the case of the JSON API, there is no such path needed: the JSON API “just is”: it doesn’t need to provide any -hypermedia representation for creating a new contact. You simply know where to issue a POST to, to create a contact, -likely through some documentation provided about the API, and that’s it.

-
-
-

Because of that fact, we can put the “create” handler on the same path as the “list” handler: /api/v1/contacts, but -have it respond only to HTTP POST requests.

-
-
-

The code here is relatively straightforward: populate a new contact with the information from the POST request, -attempt to save it, and — if it is not successful — show error messages. Here is the code:

-
-
-
Adding Contacts With Our JSON API
-
-
@app.route("/api/v1/contacts", methods=["POST"]) (1)
+                        
+
+
+

So, you can see, we now have a way to get a relatively simple JSON representation of our + contacts via an HTTP request. + Not perfect, but good enough for the purposes of this book! It’s certainly good enough + to write some basic automated + scripts against.

+
+
+

For example:

+
+
+
    +
  • +

    You could move your contacts to another system on a nightly basis

    +
  • +
  • +

    You could back your contacts up to a local file

    +
  • +
  • +

    You could automate an email blast to your contacts

    +
  • +
+
+
+

Having this small JSON Data API opens up a lot of automation possibilities that would be + messier to achieve with our existing + hypermedia API.

+
+
+
+

Adding Contacts

+
+

Let’s move on the next piece of functionality: adding a new contact to the system. Once + again, our code is going + to look similar in some ways to the code that we wrote for our normal web application. + However, here we are also + going to see the JSON API and the hypermedia API for our web application begin to obviously + diverge.

+
+
+

In the web application, we needed a separate path, /contacts/new to host the + HTML form for creating a new contact. In + the web application we made the decision to issue a POST to that same path to + keep things consistent.

+
+
+

In the case of the JSON API, there is no such path needed: the JSON API “just + is”: it doesn’t need to provide any + hypermedia representation for creating a new contact. You simply know where to issue a + POST to, to create a contact, + likely through some documentation provided about the API, and that’s it.

+
+
+

Because of that fact, we can put the “create” handler on the same path as the + “list” handler: /api/v1/contacts, but + have it respond only to HTTP POST requests.

+
+
+

The code here is relatively straightforward: populate a new contact with the information from + the POST request, + attempt to save it, and — if it is not successful — show + error messages. Here is the code:

+
+
+
Adding Contacts With Our JSON API
+
+
@app.route("/api/v1/contacts", methods=["POST"]) (1)
 def json_contacts_new():
     c = Contact(None, request.form.get('first_name'), request.form.get('last_name'), request.form.get('phone'),
                 request.form.get('email')) (2)
@@ -12980,102 +18910,120 @@ def json_contacts_new():
         return c.__dict__
     else:
         return {"errors": c.errors}, 400 (4)
-
-
-
-
    -
  1. -

    This handler is on the same path as the first one for our JSON API, but handles POST requests.

    -
  2. -
  3. -

    We create a new Contact based on values submitted with the request.

    -
  4. -
  5. -

    We attempt to save the contact and, if successful, render it as a JSON object.

    -
  6. -
  7. -

    If the save is not successful, we render an object showing the errors, with a response code of 400 (Bad Request).

    -
  8. -
-
-
-

In some ways this is similar to our contacts_new() handler from our web application; we are creating the contact and attempting -to save it. In other ways it is very different:

-
-
-
    -
  • -

    There is no redirection happening here on a successful creation, because we are not dealing with a hypermedia client -like the browser.

    -
  • -
  • -

    In the case of a bad request, we simply return an error response code, 400 (Bad Request). This is in contrast with -the web application, where we simply re-render the form with error messages in it.

    -
  • -
-
-
-

These sorts of differences, over time, build up and make the idea of keeping your JSON and hypermedia APIs -on the same set of URLs less and less appealing.

-
-
-
-

Viewing Contact Details

-
-

Next, let’s make it possible for a JSON API client to download the details for a single contact. We will naturally use an -HTTP GET for this functionality and will follow the convention we established for our regular web application, and -put the path at /api/v1/contacts/<contact id>. So, for example, if you want to see the details of the contact with the -id 42, you would issue an HTTP GET to /api/v1/contacts/42.

-
-
-

This code is quite simple:

-
-
-
Getting the Details of a Contact in JSON
-
-
@app.route("/api/v1/contacts/<contact_id>", methods=["GET"]) (1)
+                        
+
+
+
    +
  1. +

    This handler is on the same path as the first one for our JSON API, but handles + POST requests.

    +
  2. +
  3. +

    We create a new Contact based on values submitted with the request.

    +
  4. +
  5. +

    We attempt to save the contact and, if successful, render it as a JSON object.

    +
  6. +
  7. +

    If the save is not successful, we render an object showing the errors, with a + response code of 400 (Bad Request).

    +
  8. +
+
+
+

In some ways this is similar to our contacts_new() handler from our web + application; we are creating the contact and attempting + to save it. In other ways it is very different:

+
+
+
    +
  • +

    There is no redirection happening here on a successful creation, because we are not + dealing with a hypermedia client + like the browser.

    +
  • +
  • +

    In the case of a bad request, we simply return an error response code, + 400 (Bad Request). This is in contrast with + the web application, where we simply re-render the form with error messages in it. +

    +
  • +
+
+
+

These sorts of differences, over time, build up and make the idea of keeping your JSON and + hypermedia APIs + on the same set of URLs less and less appealing.

+
+
+
+

Viewing Contact Details

+
+

Next, let’s make it possible for a JSON API client to download the details for a single + contact. We will naturally use an + HTTP GET for this functionality and will follow the convention we established + for our regular web application, and + put the path at /api/v1/contacts/<contact id>. So, for example, if you + want to see the details of the contact with the + id 42, you would issue an HTTP GET to + /api/v1/contacts/42.

+
+
+

This code is quite simple:

+
+
+
Getting the Details of a Contact in JSON
+
+
@app.route("/api/v1/contacts/<contact_id>", methods=["GET"]) (1)
 def json_contacts_view(contact_id=0):
     contact = Contact.find(contact_id) (2)
     return contact.__dict__ (3)
-
-
-
-
    -
  1. -

    Add a new GET route at the path we want to use for viewing contact details

    -
  2. -
  3. -

    Look the contact up via the id passed in through the path

    -
  4. -
  5. -

    Convert the contact to a dictionary, so it can be rendered as JSON response

    -
  6. -
-
-
-

Nothing too complicated: we look the contact up by ID, provided in the path to the controller, and look that contact up. -We then render it as JSON. You have to appreciate the simplicity of this code!

-
-
-

Next, let’s add updating and deleting a contact as well.

-
-
-
-

Updating & Deleting Contacts

-
-

As with the create contact API end point, because there is no HTML UI to produce for them, we can reuse the -/api/v1/contacts/<contact id> path. We will use the PUT HTTP method for updating a contact and the DELETE -method for deleting one.

-
-
-

Our update code is going to look nearly identical to the create handler, except that, rather than creating a new contact, -we will look up the contact by ID and update its fields. In this sense we are just combining the code of the create -handler and the detail view handler.

-
-
-
Updating A Contact With Our JSON API
-
-
@app.route("/api/v1/contacts/<contact_id>", methods=["PUT"]) (1)
+                        
+
+
+
    +
  1. +

    Add a new GET route at the path we want to use for viewing contact + details

    +
  2. +
  3. +

    Look the contact up via the id passed in through the path

    +
  4. +
  5. +

    Convert the contact to a dictionary, so it can be rendered as JSON response

    +
  6. +
+
+
+

Nothing too complicated: we look the contact up by ID, provided in the path to the + controller, and look that contact up. + We then render it as JSON. You have to appreciate the simplicity of this code!

+
+
+

Next, let’s add updating and deleting a contact as well.

+
+
+
+

Updating & Deleting Contacts

+
+

As with the create contact API end point, because there is no HTML UI to produce for them, we + can reuse the + /api/v1/contacts/<contact id> path. We will use the PUT HTTP + method for updating a contact and the DELETE + method for deleting one. +

+
+
+

Our update code is going to look nearly identical to the create handler, except that, rather + than creating a new contact, + we will look up the contact by ID and update its fields. In this sense we are just combining + the code of the create + handler and the detail view handler.

+
+
+
Updating A Contact With Our JSON API
+
+
@app.route("/api/v1/contacts/<contact_id>", methods=["PUT"]) (1)
 def json_contacts_edit(contact_id):
     c = Contact.find(contact_id) (2)
     c.update(request.form['first_name'], request.form['last_name'], request.form['phone'], request.form['email']) (3)
@@ -13083,432 +19031,533 @@ def json_contacts_edit(contact_id):
         return c.__dict__
     else:
         return {"errors": c.errors}, 400
-
-
-
-
    -
  1. -

    We handle PUT requests to the URL for a given contact

    -
  2. -
  3. -

    Look the contact up via the id passed in through the path

    -
  4. -
  5. -

    We update the contact’s data from the values included in the request

    -
  6. -
  7. -

    From here on the logic is identical to the json_contacts_create() handler

    -
  8. -
-
-
-

Once again, very regular and, thanks to the built-in functionality in Flask, simple to implement.

-
-
-

Let’s look at deleting a contact now. This turns out to be even simpler: as with the update handler we are going to -look up the contact by id, and then, well, delete it. At that point we can return a simple JSON object indicating -success.

-
-
-
Deleting A Contact With Our JSON API
-
-
@app.route("/api/v1/contacts/<contact_id>", methods=["DELETE"]) (1)
+                        
+
+
+
    +
  1. +

    We handle PUT requests to the URL for a given contact

    +
  2. +
  3. +

    Look the contact up via the id passed in through the path

    +
  4. +
  5. +

    We update the contact’s data from the values included in the request

    +
  6. +
  7. +

    From here on the logic is identical to the json_contacts_create() + handler

    +
  8. +
+
+
+

Once again, very regular and, thanks to the built-in functionality in Flask, simple to + implement.

+
+
+

Let’s look at deleting a contact now. This turns out to be even simpler: as with the + update handler we are going to + look up the contact by id, and then, well, delete it. At that point we can return a simple + JSON object indicating + success.

+
+
+
Deleting A Contact With Our JSON API
+
+
@app.route("/api/v1/contacts/<contact_id>", methods=["DELETE"]) (1)
 def json_contacts_delete(contact_id=0):
     contact = Contact.find(contact_id)
     contact.delete() (2)
     return jsonify({"success": True}) (3)
-
-
-
-
    -
  1. -

    We handle DELETE requests to the URL for a given contact

    -
  2. -
  3. -

    Look the contact up and invoke the delete() method on it

    -
  4. -
  5. -

    Return a simple JSON object indicating that the contact was successfully deleted

    -
  6. -
-
-
-

And, with that, we have our simple little JSON Data API to live alongside our regular web application, nicely separated -out from the main web application, so it can evolve separately as needed.

-
-
-
-

Additional Data API Considerations

-
-

Now, we obviously have a lot more to do if we want to make this a production ready JSON API:

-
-
-
    -
  • -

    We don’t have any rate limiting, which is important for any publicly facing Data API to avoid abusive clients.

    -
  • -
  • -

    Even more crucially, there is currently no authentication mechanism. (We don’t have one for our web application either!)

    -
  • -
  • -

    We currently don’t support paging of our contact data.

    -
  • -
  • -

    Lots of small issues that we aren’t addressing, such as rendering a proper 404 (Not Found) response if someone makes -a request with a contact id that doesn’t exist.

    -
  • -
-
-
-

A full discussion around all of these topics is beyond the scope of this book, but we’d like to focus in on one in -particular, authentication, in order to show the difference between our hypermedia and JSON API. In order to secure -our application we need to add authentication, some mechanism for determining who a request is coming from, and -also authorization, determining if they have the right to perform the request.

-
-
-

We will set authorization aside for now and consider only authentication.

-
-
-
Authentication in Web Applications
-
-

In the HTML web application world, authentication has traditionally been done via a login page that asks a user for -their username (often their email) and a password. This password is then checked against a database of (hashed) -passwords to establish that the user is who they say they are. If the password is correct, then a session cookie -is established, indicating who the user is. This cookie is then sent with every request that the user makes to -the web application, allowing the application to know which user is making a given request.

-
- -
-

In comparison with the typical web application approach to authentication, a JSON API will typically use some sort of -token based authentication: an authentication token will be established via a mechanism like OAuth, and that authentication -token will then be passed, often as an HTTP Header, with every request that a client makes.

-
-
-

At a high level this is similar to what happens in normal web application authentication: a token is established somehow -and then then token is part of every request. However, in practice, the mechanics tend to be wildly different:

-
-
-
    -
  • -

    Cookies are part of the HTTP specification and can be easily set by an HTTP Server

    -
  • -
  • -

    JSON Authentication tokens, in contrast, often require elaborate exchange mechanics like OAuth to be established

    -
  • -
-
-
-

These differing mechanics for establishing authentication are yet another good reason for splitting our JSON and hypermedia -APIs up.

-
-
-
-
-

The “Shape” of Our Two APIs

-
-

When we were building out our API, we noted that in many cases the JSON API didn’t require as many end points as our -hypermedia API did: we didn’t need a /contacts/new handler, for example, to provide a hypermedia representation for -creating contacts.

-
-
-

Another aspect of our hypermedia API to consider was the performance improvement we made: we pulled the total contact count -out to a separate end point and implemented the “Lazy Load” pattern, to improve the perceived performance of our -application.

-
-
-

Now, if we had both our hypermedia and JSON API sharing the same paths, would we want to publish this API as a JSON -end point as well?

-
-
-

Maybe, but maybe not. This was a pretty specific need for our web application, and, absent a request from a user of -our JSON API, it doesn’t make sense to include it for JSON consumers.

-
-
-

And what if, by some miracle, the performance issues with Contact.count() that we were addressing with the Lazy Load -pattern goes away? Well, in our Hypermedia Driven Application we can simply revert to the old code and include the -count directly in the request to /contacts. We can remove the contacts/count end point and all the logic associated -with it. Because of the uniform interface of hypermedia, the system will continue to work just fine.

-
-
-

But what if we had tied our JSON API and hypermedia API together, and published /contacts/count as a supported end -point for our JSON API? In that case we couldn’t simply remove the end point: a (non-hypermedia) client might be -relying on it.

-
-
-

Once again you can see the flexibility of the hypermedia approach and why separating your JSON API out from your -hypermedia API lets you take maximum advantage of that flexibility.

-
-
-
-

The Model View Controller (MVC) Paradigm

-
-

One thing you may have noticed about the handlers for our JSON API is that they are relatively simple and regular. -Most of the hard work of updating data and so forth is done within the contact model itself: the handlers act as simple -connectors that provide a go-between the HTTP requests and the model.

-
-
-

This is the ideal controller of the Model-View-Controller (MVC) paradigm that was so popular in the early web: a controller -should be “thin”, with the model containing the majority of the logic in the system.

-
- -
-

Thin controllers make it easy to split your JSON and hypermedia APIs out, because all the important logic lives in the domain -model that is shared by both. This allows you to evolve both separately, while still keeping logic in sync with one -another.

-
-
-

With properly built “thin” controllers and “fat” models, keeping two separate APIs both in sync and yet -still evolving separately is not as difficult or as crazy as it might sound at first.

-
-
-
-
-
-

Bringing Hypermedia To Mobile

-
-

12. Hyperview: A Mobile Hypermedia

-
-
-

This chapter covers

-
-
-
    -
  • -

    Shortcomings with the current state of mobile app development

    -
  • -
  • -

    How a hypermedia architecture can address these problems

    -
  • -
  • -

    Hyperview as a mobile app framework that uses the hypermedia architecture

    -
  • -
  • -

    An overview of HXML, the hypermedia format used by Hyperview

    -
  • -
-
-
-

You may be forgiven for thinking the hypermedia architecture is synonymous with the Web, web browsers, and HTML. -No doubt, the Web is the largest hypermedia system, and web browsers are the most popular hypermedia client. -The dominance of the Web in discussions about hypermedia make it easy to forget that hypermedia is a general concept, and can be applied to all types of platforms and applications. -In this chapter, we will see the hypermedia architecture applied to a non-web platform: native mobile applications. -Mobile as a platform has different constraints than the Web. -It requires different trade-offs and design decisions. -Nonetheless, the concepts of hypermedia, HATEOAS, and REST can be directly applied to build delightful mobile applications!

-
-
-

12.1. The state of mobile app development

-
-

Before we can discuss how to apply hypermedia to mobile platforms, we need to understand how native mobile apps are commonly built. -I’m using the word “native” to refer to code written against an SDK provided by the phone’s operating system (typically Android or iOS). -This code is packaged into an executable binary, and uploaded & approved through app stores controlled by Google and Apple. -When users install or update an app, they’re downloading this executable and running the code directly on their device’s OS. -In this way, mobile apps have a lot in common with old-school desktop apps for Mac, Windows, or Linux. -There is one important difference between PC desktop apps of yesteryear and today’s mobile apps. -These days, almost all mobile apps are “networked”. -By networked, we mean the app needs to read and write data over the Internet to deliver its core functionality. -In other words, a networked mobile app needs to implement the client-server architecture.

-
-
-

When implementing the client-server architecture, the developer needs to make a decision: Should the app be designed as a thin client or thick client? -The current mobile ecosystems strongly push developers towards a thick-client approach. -Why? -Remember, Android and iOS require that a native mobile app be packaged and distributed as an executable binary. -There’s no way around it. -Since the developer needs to write code to package into an executable, it seems logical to implement some of the app’s logic in that code. -The code may as well initiate HTTP calls to the server to retrieve data, and then render that data using the platform’s UI libraries. -Thus, developers are naturally led into a thick-client pattern that looks something like this:

-
-
-
    -
  • -

    The client contains code to make API requests to the server, and code to translate those responses to UI updates

    -
  • -
  • -

    The server implements an HTTP API that speaks JSON, and knows little about the state of the client

    -
  • -
-
-
-

Just like with SPAs on the web, this architecture has a big downside: the app’s logic gets spread across the client and server. -Sometimes, this means that logic gets duplicated (like to validate form data). -Other times, the client and server each implement disjoint parts of the app’s overall logic. -To understand what the app does, a developer needs to trace interactions between two very different codebases.

-
-
-

There’s another downside that affects mobile apps more than SPAs: API churn. -Remember, the app stores control how your app gets distributed and updated. -Users can even control if and when they get updated versions of your app. -As a mobile developer, you can’t assume that every user will be on the latest version of your app. -Your frontend code gets fragmented across many versions, and now your backend needs to support all of them.

-
-
-
-

12.2. Hypermedia for Mobile Apps

-
-

We’ve seen that the hypermedia architecture can address the shortcomings of SPAs on the web. -But can hypermedia work for mobile apps as well? -The answer is yes!

-
-
-

Just like on the web, we can use Hypermedia formats on mobile and let it serve as the engine of application state. -All of the logic is controlled from the backend, rather than being spread between two codebases. -Hypermedia architecture also solves the annoying problem of API churn on mobile apps. -Since the backend serves a Hypermedia response containing both data and actions, there’s no way for the data and UI to get out of sync. -No more worries about backwards compatibility or maintaining multiple API versions.

-
-
-

So how can you use Hypermedia for your mobile app? -There are two approaches employing hypermedia to build & ship native mobile apps today:

-
-
-
    -
  • -

    Web views, which wraps the trusty web platform in a mobile app shell

    -
  • -
  • -

    Hyperview, a new hypermedia format we designed specifically for mobile apps

    -
  • -
-
-
-

Web views

-
-

The simplest way to use hypermedia architecture on mobile is by leveraging web technologies. -Both Android and iOS SDKs provide “web views”: chromeless web browsers that can be embedded in native apps. -Tools like Apache Cordova make it easy to take the URL of a website, and spit out native iOS and Android apps based on web views. -If you already have a responsive web app, you can get a “native” Hypermedia apps for free. -Sounds too good to be true, right?

-
-
-

Of course, there is a fundamental limitation with this approach. -The web and mobile are different platforms, with different capabilities and UX conventions. -HTML doesn’t natively support common UI patterns of mobile apps. -One of the biggest differences is around how each platform handles navigation. -On the web, navigation is page-based, with one page replacing another and the browser providing back/forward buttons to navigate the page history. -On mobile, navigation is more complex, and tuned for the physicality of gesture-based interactions.

-
-
-
    -
  • -

    To drill down, screens slide on top of each other, forming stacks of screens.

    -
  • -
  • -

    A nav bar at the bottom of the app allows switching between various stacks of screens.

    -
  • -
  • -

    Modals slide up from the bottom of the app, covering the other stacks and the nav bar.

    -
  • -
  • -

    Unlike with web pages, all of these screens are still present in memory, rendered and updating based on app state.

    -
  • -
-
-
-

The navigation architecture is a major difference between how mobile and web apps function. -But it’s not the only one. -Many other UX patterns are present in mobile apps, but are not natively supported on the web:

-
-
-
    -
  • -

    pull-to-refresh to refresh content in a screen

    -
  • -
  • -

    horizontal swipe on UI elements to reveal actions

    -
  • -
  • -

    sectioned lists with sticky headers

    -
  • -
-
-
-

While these interactions are not natively supported by web browsers, they can be simulated with JS libraries. -Of course, these libraries will never have the same feel and performance as native gestures. -And using these libraries usually requires embracing a JS-heavy SPA architecture. -This puts us back at square 1! -To avoid using the typical thick-client architecture of native mobile apps, we turned to a web view. -The web view allows us to use good-old hypermedia-based HTML. -But to get the desired look & feel of a mobile app, we end up building a SPA in JS, losing the benefits of Hypermedia in the process.

-
-
-

To build a hypermedia-based mobile app that feels and acts native, HTML isn’t going to cut it. -We need a format designed to represent the interactions and patterns of native mobile apps. -That’s exactly what Hyperview does.

-
-
-
-

Hyperview

-
-

Hyperview is an open-source framework that provides:

-
-
-
    -
  • -

    A pre-defined hypermedia format for defining mobile apps called HXML

    -
  • -
  • -

    A hypermedia client for HXML that can be embedded in an app binary on iOS and Android

    -
  • -
  • -

    Extension points in HXML and the client to customize the framework for a given app

    -
  • -
-
-
-
The Format
-
-

HXML was designed to feel familiar to web developers, used to working with HTML. -Thus the choice of XML for the base format. -In addition to familiar ergonomics, XML is compatible with server-side rendering libraries. -For example, Jinja2 is perfectly suited as a templating library to render HXML. -The familiarity of XML and the ease of integration on the backend make it simple to adopt in both new and existing codebases. -Take a look at a “Hello World” app written in HXML. -The syntax should be familiar to anyone who’s worked with HTML:

-
-
-
Hello World
-
-
<doc xmlns="https://hyperview.org/hyperview">
+                        
+
+
+
    +
  1. +

    We handle DELETE requests to the URL for a given contact

    +
  2. +
  3. +

    Look the contact up and invoke the delete() method on it

    +
  4. +
  5. +

    Return a simple JSON object indicating that the contact was successfully deleted

    +
  6. +
+
+
+

And, with that, we have our simple little JSON Data API to live alongside our regular web + application, nicely separated + out from the main web application, so it can evolve separately as needed.

+
+
+
+

Additional Data API Considerations

+
+

Now, we obviously have a lot more to do if we want to make this a production ready JSON API: +

+
+
+
    +
  • +

    We don’t have any rate limiting, which is important for any publicly facing + Data API to avoid abusive clients.

    +
  • +
  • +

    Even more crucially, there is currently no authentication mechanism. (We don’t + have one for our web application either!)

    +
  • +
  • +

    We currently don’t support paging of our contact data.

    +
  • +
  • +

    Lots of small issues that we aren’t addressing, such as rendering a proper + 404 (Not Found) response if someone makes + a request with a contact id that doesn’t exist.

    +
  • +
+
+
+

A full discussion around all of these topics is beyond the scope of this book, but we’d + like to focus in on one in + particular, authentication, in order to show the difference between our hypermedia and JSON + API. In order to secure + our application we need to add authentication, some mechanism for determining who a + request is coming from, and + also authorization, determining if they have the right to perform the request.

+
+
+

We will set authorization aside for now and consider only authentication.

+
+
+
Authentication in Web Applications
+
+

In the HTML web application world, authentication has traditionally been done via a login + page that asks a user for + their username (often their email) and a password. This password is then checked against + a database of (hashed) + passwords to establish that the user is who they say they are. If the password is + correct, then a session cookie + is established, indicating who the user is. This cookie is then sent with every request + that the user makes to + the web application, allowing the application to know which user is making a given + request.

+
+ +
+

In comparison with the typical web application approach to authentication, a JSON API + will typically use some sort of + token based authentication: an authentication token will be established via a + mechanism like OAuth, and that authentication + token will then be passed, often as an HTTP Header, with every request that a client + makes. +

+
+
+

At a high level this is similar to what happens in normal web application authentication: + a token is established somehow + and then then token is part of every request. However, in practice, the mechanics tend + to be wildly different:

+
+
+
    +
  • +

    Cookies are part of the HTTP specification and can be easily set by an + HTTP Server

    +
  • +
  • +

    JSON Authentication tokens, in contrast, often require elaborate exchange + mechanics like OAuth to be established

    +
  • +
+
+
+

These differing mechanics for establishing authentication are yet another good reason for + splitting our JSON and hypermedia + APIs up.

+
+
+
+
+

The “Shape” of Our Two APIs

+
+

When we were building out our API, we noted that in many cases the JSON API didn’t + require as many end points as our + hypermedia API did: we didn’t need a /contacts/new handler, for example, + to provide a hypermedia representation for + creating contacts.

+
+
+

Another aspect of our hypermedia API to consider was the performance improvement we made: we + pulled the total contact count + out to a separate end point and implemented the “Lazy Load” pattern, to improve + the perceived performance of our + application.

+
+
+

Now, if we had both our hypermedia and JSON API sharing the same paths, would we want to + publish this API as a JSON + end point as well?

+
+
+

Maybe, but maybe not. This was a pretty specific need for our web application, and, absent a + request from a user of + our JSON API, it doesn’t make sense to include it for JSON consumers.

+
+
+

And what if, by some miracle, the performance issues with Contact.count() that + we were addressing with the Lazy Load + pattern goes away? Well, in our Hypermedia Driven Application we can simply revert to the + old code and include the + count directly in the request to /contacts. We can remove the + contacts/count end point and all the logic associated + with it. Because of the uniform interface of hypermedia, the system will continue to work + just fine.

+
+
+

But what if we had tied our JSON API and hypermedia API together, and published + /contacts/count as a supported end + point for our JSON API? In that case we couldn’t simply remove the end point: a + (non-hypermedia) client might be + relying on it.

+
+
+

Once again you can see the flexibility of the hypermedia approach and why separating your + JSON API out from your + hypermedia API lets you take maximum advantage of that flexibility.

+
+
+
+

The Model View Controller (MVC) Paradigm

+
+

One thing you may have noticed about the handlers for our JSON API is that they are + relatively simple and regular. + Most of the hard work of updating data and so forth is done within the contact model itself: + the handlers act as simple + connectors that provide a go-between the HTTP requests and the model.

+
+
+

This is the ideal controller of the Model-View-Controller (MVC) paradigm that was so popular + in the early web: a controller + should be “thin”, with the model containing the majority of the logic in the + system.

+
+ +
+

Thin controllers make it easy to split your JSON and hypermedia APIs out, because all the + important logic lives in the domain + model that is shared by both. This allows you to evolve both separately, while still keeping + logic in sync with one + another.

+
+
+

With properly built “thin” controllers and “fat” models, keeping two + separate APIs both in sync and yet + still evolving separately is not as difficult or as crazy as it might sound at first.

+
+
+
+
+
+

Bringing Hypermedia To Mobile

+
+

12. Hyperview: A Mobile Hypermedia

+
+
+

This chapter covers

+
+
+
    +
  • +

    Shortcomings with the current state of mobile app development

    +
  • +
  • +

    How a hypermedia architecture can address these problems

    +
  • +
  • +

    Hyperview as a mobile app framework that uses the hypermedia architecture

    +
  • +
  • +

    An overview of HXML, the hypermedia format used by Hyperview

    +
  • +
+
+
+

You may be forgiven for thinking the hypermedia architecture is synonymous with the Web, web + browsers, and HTML. + No doubt, the Web is the largest hypermedia system, and web browsers are the most popular hypermedia + client. + The dominance of the Web in discussions about hypermedia make it easy to forget that hypermedia is a + general concept, and can be applied to all types of platforms and applications. + In this chapter, we will see the hypermedia architecture applied to a non-web platform: native + mobile applications. + Mobile as a platform has different constraints than the Web. + It requires different trade-offs and design decisions. + Nonetheless, the concepts of hypermedia, HATEOAS, and REST can be directly applied to build + delightful mobile applications!

+
+
+

12.1. The state of mobile app development

+
+

Before we can discuss how to apply hypermedia to mobile platforms, we need to understand how + native mobile apps are commonly built. + I’m using the word “native” to refer to code written against an SDK provided + by the phone’s operating system (typically Android or iOS). + This code is packaged into an executable binary, and uploaded & approved through app stores + controlled by Google and Apple. + When users install or update an app, they’re downloading this executable and running the + code directly on their device’s OS. + In this way, mobile apps have a lot in common with old-school desktop apps for Mac, Windows, or + Linux. + There is one important difference between PC desktop apps of yesteryear and today’s mobile + apps. + These days, almost all mobile apps are “networked”. + By networked, we mean the app needs to read and write data over the Internet to deliver its core + functionality. + In other words, a networked mobile app needs to implement the client-server architecture.

+
+
+

When implementing the client-server architecture, the developer needs to make a decision: Should + the app be designed as a thin client or thick client? + The current mobile ecosystems strongly push developers towards a thick-client approach. + Why? + Remember, Android and iOS require that a native mobile app be packaged and distributed as an + executable binary. + There’s no way around it. + Since the developer needs to write code to package into an executable, it seems logical to + implement some of the app’s logic in that code. + The code may as well initiate HTTP calls to the server to retrieve data, and then render that + data using the platform’s UI libraries. + Thus, developers are naturally led into a thick-client pattern that looks something like this: +

+
+
+
    +
  • +

    The client contains code to make API requests to the server, and code to translate those + responses to UI updates

    +
  • +
  • +

    The server implements an HTTP API that speaks JSON, and knows little about the state of + the client

    +
  • +
+
+
+

Just like with SPAs on the web, this architecture has a big downside: the app’s logic gets + spread across the client and server. + Sometimes, this means that logic gets duplicated (like to validate form data). + Other times, the client and server each implement disjoint parts of the app’s overall + logic. + To understand what the app does, a developer needs to trace interactions between two very + different codebases.

+
+
+

There’s another downside that affects mobile apps more than SPAs: API churn. + Remember, the app stores control how your app gets distributed and updated. + Users can even control if and when they get updated versions of your app. + As a mobile developer, you can’t assume that every user will be on the latest version of + your app. + Your frontend code gets fragmented across many versions, and now your backend needs to support + all of them.

+
+
+
+

12.2. Hypermedia for Mobile Apps

+
+

We’ve seen that the hypermedia architecture can address the shortcomings of SPAs on the web. + But can hypermedia work for mobile apps as well? + The answer is yes!

+
+
+

Just like on the web, we can use Hypermedia formats on mobile and let it serve as the engine of + application state. + All of the logic is controlled from the backend, rather than being spread between two codebases. + Hypermedia architecture also solves the annoying problem of API churn on mobile apps. + Since the backend serves a Hypermedia response containing both data and actions, there’s + no way for the data and UI to get out of sync. + No more worries about backwards compatibility or maintaining multiple API versions.

+
+
+

So how can you use Hypermedia for your mobile app? + There are two approaches employing hypermedia to build & ship native mobile apps today:

+
+
+
    +
  • +

    Web views, which wraps the trusty web platform in a mobile app shell

    +
  • +
  • +

    Hyperview, a new hypermedia format we designed specifically for mobile apps

    +
  • +
+
+
+

Web views

+
+

The simplest way to use hypermedia architecture on mobile is by leveraging web technologies. + Both Android and iOS SDKs provide “web views”: chromeless web browsers that can + be embedded in native apps. + Tools like Apache Cordova make it easy to take the URL of a website, and spit out native iOS + and Android apps based on web views. + If you already have a responsive web app, you can get a “native” Hypermedia apps + for free. + Sounds too good to be true, right?

+
+
+

Of course, there is a fundamental limitation with this approach. + The web and mobile are different platforms, with different capabilities and UX conventions. + HTML doesn’t natively support common UI patterns of mobile apps. + One of the biggest differences is around how each platform handles navigation. + On the web, navigation is page-based, with one page replacing another and the browser + providing back/forward buttons to navigate the page history. + On mobile, navigation is more complex, and tuned for the physicality of gesture-based + interactions.

+
+
+
    +
  • +

    To drill down, screens slide on top of each other, forming stacks of screens.

    +
  • +
  • +

    A nav bar at the bottom of the app allows switching between various stacks of + screens.

    +
  • +
  • +

    Modals slide up from the bottom of the app, covering the other stacks and the nav + bar.

    +
  • +
  • +

    Unlike with web pages, all of these screens are still present in memory, rendered and + updating based on app state.

    +
  • +
+
+
+

The navigation architecture is a major difference between how mobile and web apps function. + But it’s not the only one. + Many other UX patterns are present in mobile apps, but are not natively supported on the + web:

+
+
+
    +
  • +

    pull-to-refresh to refresh content in a screen

    +
  • +
  • +

    horizontal swipe on UI elements to reveal actions

    +
  • +
  • +

    sectioned lists with sticky headers

    +
  • +
+
+
+

While these interactions are not natively supported by web browsers, they can be simulated + with JS libraries. + Of course, these libraries will never have the same feel and performance as native gestures. + And using these libraries usually requires embracing a JS-heavy SPA architecture. + This puts us back at square 1! + To avoid using the typical thick-client architecture of native mobile apps, we turned to a + web view. + The web view allows us to use good-old hypermedia-based HTML. + But to get the desired look & feel of a mobile app, we end up building a SPA in JS, + losing the benefits of Hypermedia in the process.

+
+
+

To build a hypermedia-based mobile app that feels and acts native, HTML isn’t going to + cut it. + We need a format designed to represent the interactions and patterns of native mobile apps. + That’s exactly what Hyperview does.

+
+
+
+

Hyperview

+
+

Hyperview is an open-source framework that provides:

+
+
+
    +
  • +

    A pre-defined hypermedia format for defining mobile apps called HXML

    +
  • +
  • +

    A hypermedia client for HXML that can be embedded in an app binary on iOS and Android +

    +
  • +
  • +

    Extension points in HXML and the client to customize the framework for a given app +

    +
  • +
+
+
+
The Format
+
+

HXML was designed to feel familiar to web developers, used to working with HTML. + Thus the choice of XML for the base format. + In addition to familiar ergonomics, XML is compatible with server-side rendering + libraries. + For example, Jinja2 is perfectly suited as a templating library to render HXML. + The familiarity of XML and the ease of integration on the backend make it simple to + adopt in both new and existing codebases. + Take a look at a “Hello World” app written in HXML. + The syntax should be familiar to anyone who’s worked with HTML:

+
+
+
Hello World
+
+
<doc xmlns="https://hyperview.org/hyperview">
   <screen>
     <styles />
     <body>
@@ -13521,148 +19570,180 @@ The syntax should be familiar to anyone who’s worked with HTML:

</body> </screen> </doc>
-
-
-
-

But HXML is not just a straight port of HTML with differently named tags. -In previous chapters, we’ve seen how htmx enhances HTML with a handful of new attributes. -These additions maintain the declarative nature of HTML, while giving developers the power to create rich web apps. -In HXML, the concepts of htmx (and IntercoolerJS before it) are built into the spec. -Specifically, HXML is not limited to “click to navigate” and “press to submit” interactions like basic HTML. -It supports a range of triggers and actions for modifying the content on a screen. -These interactions are bundled together in a powerful concept of “behaviors”. -Developers can even define new behavior actions to add new capabilities to their app, without the need for scripting. -We will learn more about behaviors later in this chapter.

-
-
-
-
The client
-
-

Web developers are lucky. -They can assume their users have access to a web browser capable of rendering any web app. -In Hypermedia terms, the Hypermedia (HTML) client is already built and distributed to users. -Half the work is done! -The developer only has to build the backend to serve Hypermedia responses.

-
-
-
-figure 6 1 -
-
Figure 9. One HTML server, multiple HTML clients
-
-
-

This is possible because the web is an open ecosystem built on standards. -Any developer can build and host a web app, and any user can access it directly.

-
-
-

As we know, that’s not the case with mobile platforms. -There is no open standard for building and distributing native mobile apps. -And there’s definitely no widely distributed “HXML browser”. -So how can a developer deliver a Hypermedia mobile app using HXML? -Well, unlike on the web, the mobile developer must provide both the backend to serve HXML, and a mobile client app to render those HXML responses.

-
-
-
-figure 6 2 -
-
Figure 10. One HXML server, one HXML client
-
-
-

It would be a lot to ask from developers to write their own HXML client. -That’s why Hyperview provides an open-source client library, written in React Native. -This library can be used to bootstrap a new mobile app, or it can be embedded in an existing app. -In either case, developers get a full “HXML browser” without needing to write it from scratch.

-
- -
-
-
-

Which Hypermedia architecture should you use?

-
-

We’ve discussed two approaches for creating mobile apps using Hypermedia architecture:

-
-
-
    -
  • -

    create a backend that returns HTML, and serve it in a mobile app through a web view

    -
  • -
  • -

    create a backend that returns HXML, and serve it in a mobile app with the Hyperview client

    -
  • -
-
-
-

I purposefully described the two approaches in a way to highlight their similarities. -After all, they are both using the Hypermedia architecture, just with different formats and clients. -Both approaches solve the fundamental issues with traditional, SPA-like mobile app development:

-
-
-
    -
  • -

    The backend controls the full state of the app.

    -
  • -
  • -

    Our app’s logic is all in one place.

    -
  • -
  • -

    The app always runs the latest version, there’s no API churn to worry about

    -
  • -
-
-
-

So which approach should you use for a Hypermedia-driven mobile app? -Based on my experience building both types of apps, we strongly believe the Hyperview approach results in a better user experience. -The web-view will always feel out-of-place on iOS and Android; there’s just no good way to replicate the patterns of navigation and interaction that mobile users expect. -Hyperview was created specifically to address the limitations of thick-client and web view approaches. -After the initial investment to learn Hyperview, you’ll get all of the benefits of the Hypermedia architecture, without the downsides of a degraded user experience.

-
-
-

Of course, if you already have a simple, mobile-friendly web app, then using a web-view approach is sensible. -You will certainly save time from not having to serve your app as HXML in addition to HTML. -But as we will show at the end of this chapter, it doesn’t take a lot of work to convert an existing Hypermedia-driven web app into a Hyperview mobile app. -But before we get there, we need to introduce the concepts of elements and behaviors in Hyperview. -Then, we’ll re-build our contacts app in Hyperview.

-
-
-
-
-

12.3. Introduction to HXML

-
-

Hello World!

-
-

HXML was designed to feel natural to web developers coming from HTML. -Let’s take a closer look at the “Hello World” app defined in HXML:

-
-
-
Hello World, revisited
-
-
<doc xmlns="https://hyperview.org/hyperview"> (1)
+                            
+
+
+

But HXML is not just a straight port of HTML with differently named tags. + In previous chapters, we’ve seen how htmx enhances HTML with a handful of new + attributes. + These additions maintain the declarative nature of HTML, while giving developers the + power to create rich web apps. + In HXML, the concepts of htmx (and IntercoolerJS before it) are built into the spec. + Specifically, HXML is not limited to “click to navigate” and “press to + submit” interactions like basic HTML. + It supports a range of triggers and actions for modifying the content on a screen. + These interactions are bundled together in a powerful concept of + “behaviors”. + Developers can even define new behavior actions to add new capabilities to their app, + without the need for scripting. + We will learn more about behaviors later in this chapter.

+
+
+
+
The client
+
+

Web developers are lucky. + They can assume their users have access to a web browser capable of rendering any web + app. + In Hypermedia terms, the Hypermedia (HTML) client is already built and distributed to + users. + Half the work is done! + The developer only has to build the backend to serve Hypermedia responses.

+
+
+
+ figure 6 1 +
+
Figure 9. One HTML server, multiple HTML clients
+
+
+

This is possible because the web is an open ecosystem built on standards. + Any developer can build and host a web app, and any user can access it directly.

+
+
+

As we know, that’s not the case with mobile platforms. + There is no open standard for building and distributing native mobile apps. + And there’s definitely no widely distributed “HXML browser”. + So how can a developer deliver a Hypermedia mobile app using HXML? + Well, unlike on the web, the mobile developer must provide both the backend to serve + HXML, and a mobile client app to render those HXML responses.

+
+
+
+ figure 6 2 +
+
Figure 10. One HXML server, one HXML client
+
+
+

It would be a lot to ask from developers to write their own HXML client. + That’s why Hyperview provides an open-source client library, written in React + Native. + This library can be used to bootstrap a new mobile app, or it can be embedded in an + existing app. + In either case, developers get a full “HXML browser” without needing to + write it from scratch.

+
+ +
+
+
+

Which Hypermedia architecture should you use? +

+
+

We’ve discussed two approaches for creating mobile apps using Hypermedia architecture: +

+
+
+
    +
  • +

    create a backend that returns HTML, and serve it in a mobile app through a web view +

    +
  • +
  • +

    create a backend that returns HXML, and serve it in a mobile app with the Hyperview + client

    +
  • +
+
+
+

I purposefully described the two approaches in a way to highlight their similarities. + After all, they are both using the Hypermedia architecture, just with different formats and + clients. + Both approaches solve the fundamental issues with traditional, SPA-like mobile app + development:

+
+
+
    +
  • +

    The backend controls the full state of the app.

    +
  • +
  • +

    Our app’s logic is all in one place.

    +
  • +
  • +

    The app always runs the latest version, there’s no API churn to worry about

    +
  • +
+
+
+

So which approach should you use for a Hypermedia-driven mobile app? + Based on my experience building both types of apps, we strongly believe the Hyperview + approach results in a better user experience. + The web-view will always feel out-of-place on iOS and Android; there’s just no good + way to replicate the patterns of navigation and interaction that mobile users expect. + Hyperview was created specifically to address the limitations of thick-client and web view + approaches. + After the initial investment to learn Hyperview, you’ll get all of the benefits of the + Hypermedia architecture, without the downsides of a degraded user experience.

+
+
+

Of course, if you already have a simple, mobile-friendly web app, then using a web-view + approach is sensible. + You will certainly save time from not having to serve your app as HXML in addition to HTML. + But as we will show at the end of this chapter, it doesn’t take a lot of work to + convert an existing Hypermedia-driven web app into a Hyperview mobile app. + But before we get there, we need to introduce the concepts of elements and behaviors in + Hyperview. + Then, we’ll re-build our contacts app in Hyperview.

+
+
+
+
+

12.3. Introduction to HXML

+
+

Hello World!

+
+

HXML was designed to feel natural to web developers coming from HTML. + Let’s take a closer look at the “Hello World” app defined in HXML:

+
+
+
Hello World, revisited
+
+
<doc xmlns="https://hyperview.org/hyperview"> (1)
   <screen> (2)
     <styles />
     <body> (3)
@@ -13675,101 +19756,121 @@ Let’s take a closer look at the “Hello World” app defined in H
     </body>
   </screen>
 </doc>
-
-
-
-
    -
  1. -

    The root element of the HXML app

    -
  2. -
  3. -

    The element representing a screen of the app

    -
  4. -
  5. -

    The element representing the UI of the screen

    -
  6. -
  7. -

    The element representing the top header of the screen

    -
  8. -
  9. -

    A wrapper element around the content shown on the screen

    -
  10. -
  11. -

    The text content shown on the screen

    -
  12. -
-
-
-

Nothing too strange here, right? -Just like HTML, the syntax defines a tree of elements using start tags (<screen>) and end tags (</screen>). -Elements can contain other elements (<view>) or text (Hello World!). -Elements can also be empty, represented with an empty tag (<styles />). -However, you’ll notice that the names of the HXML element are different from those in HTML. -Let’s take a closer look at each of those elements to understand what they do.

-
-
-

<doc> is the root of the HXML app. -Think of it as equivalent to the <html> element in HTML. -Note that the <doc> element contains an attribute xmlns="https://hyperview.org/hyperview". -This defines the default namespace for the doc. -Namespaces are a feature of XML that allow one doc to contain elements defined by different developers. -To prevent conflicts when two developers use the same name for their element, each developer defines a unique namespace. -We will talk more about namespaces when we discuss custom elements & behaviors later in this chapter. -For now, it’s enough to know that elements in an HXML doc without an explicit namespace are considered to be part of the https://hyperview.org/hyperview namespace.

-
-
-

<screen> represents the UI that gets rendered on a single screen of a mobile app. -It’s possible for one <doc> to contain multiple <screen> elements, but we won’t get into that now. -Typically, a <screen> element will contain elements that define the content and styling of the screen.

-
-
-

<styles> defines the styles of the UI on the screen. -We won’t get too much into styling in Hyperview in this chapter. -Suffice it to say, unlike HTML, Hyperview does not use a separate language (CSS) to define styles. -Instead, styling rules such as colors, spacing, layout, and fonts are defined in HXML. -These rules are then explicitly referenced by UI elements, much like using classes in CSS.

-
-
-

<body> defines the actual UI of the screen. -The body includes all text, images, buttons, forms, etc that will be shown to the user. -This is equivalent to the <body> element in HTML.

-
-
-

<header> defines the header of the screen. -Typically in mobile apps, the header includes some navigation (like a back button), and the title of the screen. -It’s useful to define the header separately from the rest of the body. -Some mobile OSes will use a different transition for the header than the rest of the screen content.

-
-
-

<view> is the basic building block for layouts and structure within the screen’s body. -Think of it like a <div> in HTML. -Note that unlike in HTML, a <div> cannot directly contain text.

-
-
-

<text> elements are the only way to render text in the UI. -In this example, “Hello World” is contained within a <text> element.

-
-
-

That’s all there is to define a basic “Hello World” app in HXML. -Of course, this isn’t very exciting. -Let’s cover some other built-in display elements.

-
-
-
-

UI Elements

-
-
Lists
-
-

A very common pattern in mobile apps is to scroll through a list of items. -The physical properties of a phone screen (long & vertical) and the intuitive gesture of swiping a thumb up & down makes this a good choice for many screens.

-
-
-

HXML has dedicated elements for representing lists and items.

-
-
-
List element
-
-
<list> (1)
+                        
+
+
+
    +
  1. +

    The root element of the HXML app

    +
  2. +
  3. +

    The element representing a screen of the app

    +
  4. +
  5. +

    The element representing the UI of the screen

    +
  6. +
  7. +

    The element representing the top header of the screen

    +
  8. +
  9. +

    A wrapper element around the content shown on the screen

    +
  10. +
  11. +

    The text content shown on the screen

    +
  12. +
+
+
+

Nothing too strange here, right? + Just like HTML, the syntax defines a tree of elements using start tags + (<screen>) and end tags (</screen>). + Elements can contain other elements (<view>) or text + (Hello World!). + Elements can also be empty, represented with an empty tag (<styles />). + However, you’ll notice that the names of the HXML element are different from those in + HTML. + Let’s take a closer look at each of those elements to understand what they do.

+
+
+

<doc> is the root of the HXML app. + Think of it as equivalent to the <html> element in HTML. + Note that the <doc> element contains an attribute + xmlns="https://hyperview.org/hyperview". + This defines the default namespace for the doc. + Namespaces are a feature of XML that allow one doc to contain elements defined by different + developers. + To prevent conflicts when two developers use the same name for their element, each developer + defines a unique namespace. + We will talk more about namespaces when we discuss custom elements & behaviors later in + this chapter. + For now, it’s enough to know that elements in an HXML doc without an explicit + namespace are considered to be part of the + https://hyperview.org/hyperview + namespace.

+
+
+

<screen> represents the UI that gets rendered on a single screen of a + mobile app. + It’s possible for one <doc> to contain multiple + <screen> elements, but we won’t get into that now. + Typically, a <screen> element will contain elements that define the + content and styling of the screen.

+
+
+

<styles> defines the styles of the UI on the screen. + We won’t get too much into styling in Hyperview in this chapter. + Suffice it to say, unlike HTML, Hyperview does not use a separate language (CSS) to define + styles. + Instead, styling rules such as colors, spacing, layout, and fonts are defined in HXML. + These rules are then explicitly referenced by UI elements, much like using classes in CSS. +

+
+
+

<body> defines the actual UI of the screen. + The body includes all text, images, buttons, forms, etc that will be shown to the user. + This is equivalent to the <body> element in HTML.

+
+
+

<header> defines the header of the screen. + Typically in mobile apps, the header includes some navigation (like a back button), and the + title of the screen. + It’s useful to define the header separately from the rest of the body. + Some mobile OSes will use a different transition for the header than the rest of the screen + content.

+
+
+

<view> is the basic building block for layouts and structure within the + screen’s body. + Think of it like a <div> in HTML. + Note that unlike in HTML, a <div> cannot directly contain text.

+
+
+

<text> elements are the only way to render text in the UI. + In this example, “Hello World” is contained within a <text> + element.

+
+
+

That’s all there is to define a basic “Hello World” app in HXML. + Of course, this isn’t very exciting. + Let’s cover some other built-in display elements.

+
+
+
+

UI Elements

+
+
Lists
+
+

A very common pattern in mobile apps is to scroll through a list of items. + The physical properties of a phone screen (long & vertical) and the intuitive + gesture of swiping a thumb up & down makes this a good choice for many screens.

+
+
+

HXML has dedicated elements for representing lists and items.

+
+
+
List element
+
+
<list> (1)
   <item key="item1"> (2)
     <text>My first item</text> (3)
   </item>
@@ -13777,52 +19878,62 @@ The physical properties of a phone screen (long & vertical) and the intuitiv
     <text>My second item</text>
   </item>
 </list>
-
-
-
-
    -
  1. -

    Element representing a list

    -
  2. -
  3. -

    Element representing an item in the list, with a unique key

    -
  4. -
  5. -

    The content of the item in the list.

    -
  6. -
-
-
-

Lists are represented with two new elements. -The <list> wraps all of the items in the list. -It can be styled like a generic <view> (width, height, etc). -A <list> element only contains <item> elements. -Of course, these represent each unique item in the list. -Note that <item> is required to have a key attribute, which is unique among all items in the list.

-
-
-

You might be asking, “Why do we need a custom syntax for lists of items? -Can’t we just use a bunch of <view> elements?”. -Yes, for lists with a small number of items, using nested <views> will work quite well. -However, often the number of items in a list can be long enough to require optimizations to support smooth scrolling interactions. -Consider browsing a feed of posts in a social media app. -As you keep scrolling through the feed, it’s not unusual for the app to show hundreds if not thousands of posts. -At any time, you can flick your finger to scroll to almost any part of the feed. -Mobile devices tend to be memory-constrained. -Keeping the fully-rendered list of items in memory could consume more resources than available. -That’s why both iOS and Android provide APIs for optimized list UIs. -These APIs know which part of the list is currently on-screen. To save memory, they clear out the non-visible list items, and recycle the item UI objects to conserve memory. -By using explicit <list> and <item> elements in HXML, the Hyperview client knows to use these optimized list APIs to make your app more performant.

-
-
-

It’s also worth mentioning that HXML supports section lists. -Section lists are useful for building list-based UIs, where the items in the list can be grouped for the user’s convenience. -For example, a UI showing a restaurant menu could group the offerings by dish type:

-
-
-
Section list element
-
-
<section-list> (1)
+                            
+
+
+
    +
  1. +

    Element representing a list

    +
  2. +
  3. +

    Element representing an item in the list, with a unique key

    +
  4. +
  5. +

    The content of the item in the list.

    +
  6. +
+
+
+

Lists are represented with two new elements. + The <list> wraps all of the items in the list. + It can be styled like a generic <view> (width, height, etc). + A <list> element only contains <item> elements. + Of course, these represent each unique item in the list. + Note that <item> is required to have a key attribute, + which is unique among all items in the list.

+
+
+

You might be asking, “Why do we need a custom syntax for lists of items? + Can’t we just use a bunch of <view> elements?”. + Yes, for lists with a small number of items, using nested <views> + will work quite well. + However, often the number of items in a list can be long enough to require optimizations + to support smooth scrolling interactions. + Consider browsing a feed of posts in a social media app. + As you keep scrolling through the feed, it’s not unusual for the app to show + hundreds if not thousands of posts. + At any time, you can flick your finger to scroll to almost any part of the feed. + Mobile devices tend to be memory-constrained. + Keeping the fully-rendered list of items in memory could consume more resources than + available. + That’s why both iOS and Android provide APIs for optimized list UIs. + These APIs know which part of the list is currently on-screen. To save memory, they + clear out the non-visible list items, and recycle the item UI objects to conserve + memory. + By using explicit <list> and <item> elements in + HXML, the Hyperview client knows to use these optimized list APIs to make your app more + performant.

+
+
+

It’s also worth mentioning that HXML supports section lists. + Section lists are useful for building list-based UIs, where the items in the list can be + grouped for the user’s convenience. + For example, a UI showing a restaurant menu could group the offerings by dish type:

+
+
+
Section list element
+
+
<section-list> (1)
   <section> (2)
     <section-title> (3)
       <text>Appetizers</text>
@@ -13844,131 +19955,157 @@ For example, a UI showing a restaurant menu could group the offerings by dish ty
     </item>
   </section>
 </section-list>
-
-
-
-
    -
  1. -

    Element representing a list with sections

    -
  2. -
  3. -

    The first section of appetizer offerings

    -
  4. -
  5. -

    Element for the title of the section, rendering the text “Appetizers”

    -
  6. -
  7. -

    An item representing an appetizer

    -
  8. -
  9. -

    A section for entree offerings

    -
  10. -
-
-
-

You’ll notice a couple of differences between <list> and <section-list>. -The section list element only contains <section> elements, representing a group of items. -A section can contain a <section-title> element. This is used to render some UI that acts as the header of the section. -This header is “sticky”, meaning it stays on screen while scrolling through items that belong to the corresponding section. -Finally, <item> elements act the same as in the regular list, but can only appear within a <section>.

-
-
-
-
Images
-
-

Showing images in Hyperview is pretty similar to HTML, but there are a few differences.

-
-
-
Image element
-
-
<image source="/profiles/1.jpg" style="avatar" />
-
-
-
-

The source attribute specifies how to load the image. -Like in HTML, the source can be an absolute or relative URL. -Additionally, the source can be an encoded data URI, for example data:image/png;base64,iVBORw. -However, the source can also be a “local” URL, referring to an image that is bundled as an asset in the mobile app. -The local URL is prefixed with ./:

-
-
-
Image element, pointing to local source
-
-
<image source="./logo.png" style="logo" />
-
-
-
-

Using Local URLs is an optimization. -Since the images are on the mobile device, they don’t require a network request and will appear quickly. -However, bundling the image with the mobile app binary increases the binary size. -Using local images is a good trade-off for images that are frequently accessed but rarely change. -Good examples include the app logo, or common button icons.

-
-
-

The other thing to note is the presence of the style attribute on the <image> element. -In HXML, images are required to have a style that has rules for the image’s width and height. -This is different from HTML, where <img> elements do not need to explicitly set a width and height. -Web browsers will re-flow the content of a web page once the image is fetched and the dimensions are known. -While re-flowing content is a reasonable behavior for web-based documents, users do not expect mobile apps to re-flow as content loads. -To maintain a static layout, HXML requires the dimensions to be known before the image loads.

-
-
-
-
-

Inputs

-
-

There’s a lot to cover about inputs in Hyperview. -Since this is meant to be an introduction and not an exhaustive resource, I’ll highlight just a few types of inputs. -Let’s start with an example of the simplest type of input, a text field.

-
-
-
Text field element
-
-
<text-field
+                            
+
+
+
    +
  1. +

    Element representing a list with sections

    +
  2. +
  3. +

    The first section of appetizer offerings

    +
  4. +
  5. +

    Element for the title of the section, rendering the text “Appetizers” +

    +
  6. +
  7. +

    An item representing an appetizer

    +
  8. +
  9. +

    A section for entree offerings

    +
  10. +
+
+
+

You’ll notice a couple of differences between <list> and + <section-list>. + The section list element only contains <section> elements, + representing a group of items. + A section can contain a <section-title> element. This is used to + render some UI that acts as the header of the section. + This header is “sticky”, meaning it stays on screen while scrolling through + items that belong to the corresponding section. + Finally, <item> elements act the same as in the regular list, but can + only appear within a <section>.

+
+
+
+
Images
+
+

Showing images in Hyperview is pretty similar to HTML, but there are a few differences. +

+
+
+
Image element
+
+
<image source="/profiles/1.jpg" style="avatar" />
+
+
+
+

The source attribute specifies how to load the image. + Like in HTML, the source can be an absolute or relative URL. + Additionally, the source can be an encoded data URI, for example + data:image/png;base64,iVBORw. + However, the source can also be a “local” URL, referring to an image that is + bundled as an asset in the mobile app. + The local URL is prefixed with ./:

+
+
+
Image element, pointing to local source
+
+
<image source="./logo.png" style="logo" />
+
+
+
+

Using Local URLs is an optimization. + Since the images are on the mobile device, they don’t require a network request + and will appear quickly. + However, bundling the image with the mobile app binary increases the binary size. + Using local images is a good trade-off for images that are frequently accessed but + rarely change. + Good examples include the app logo, or common button icons.

+
+
+

The other thing to note is the presence of the style attribute on the + <image> element. + In HXML, images are required to have a style that has rules for the image’s + width and height. + This is different from HTML, where <img> elements do not need to + explicitly set a width and height. + Web browsers will re-flow the content of a web page once the image is fetched and the + dimensions are known. + While re-flowing content is a reasonable behavior for web-based documents, users do not + expect mobile apps to re-flow as content loads. + To maintain a static layout, HXML requires the dimensions to be known before the image + loads.

+
+
+
+
+

Inputs

+
+

There’s a lot to cover about inputs in Hyperview. + Since this is meant to be an introduction and not an exhaustive resource, I’ll + highlight just a few types of inputs. + Let’s start with an example of the simplest type of input, a text field.

+
+
+
Text field element
+
+
<text-field
   name="first_name" (1)
   style="input" (2)
   value="Adam" (3)
   placeholder="First name" (4)
 />
-
-
-
-
    -
  1. -

    The name used when serializing data from this input

    -
  2. -
  3. -

    The style class applied to the UI element

    -
  4. -
  5. -

    The current value set in the field

    -
  6. -
  7. -

    A placeholder to display when the value is empty

    -
  8. -
-
-
-

This element should feel familiar to anyone who’s created a text field in HTML. -One difference is that most inputs in HTML use the <input> element with a type attribute, eg <input type="text">. -In Hyperview, each input has a unique name, in this case <text-field>. -By using different names, we can use more expressive XML to represent the input.

-
-
-

For example, let’s consider a case where we want to render a UI that lets the user select one among several options. -In HTML, we would use a radio button input, something like <input type="radio" name="choice" value="option1" />. -Each choice is represented as a unique input element. -This never struck me as ideal. -Most of the time, radio buttons are grouped together to affect the same name. -The HTML approach leads to a lot of boilerplate (duplication of type="radio" and name="choice" for each choice). -Also, unlike radio buttons on desktop, mobile OSes don’t provide a strong standard UI for selecting one option. -Most mobile apps use richer, custom UIs for these interactions. -So in HXML, we implement this UI using an element called <select-single>:

-
-
-
Select-single element
-
-
<select-single name="choice"> (1)
+                        
+
+
+
    +
  1. +

    The name used when serializing data from this input

    +
  2. +
  3. +

    The style class applied to the UI element

    +
  4. +
  5. +

    The current value set in the field

    +
  6. +
  7. +

    A placeholder to display when the value is empty

    +
  8. +
+
+
+

This element should feel familiar to anyone who’s created a text field in HTML. + One difference is that most inputs in HTML use the <input> element with a + type attribute, eg <input type="text">. + In Hyperview, each input has a unique name, in this case <text-field>. + By using different names, we can use more expressive XML to represent the input.

+
+
+

For example, let’s consider a case where we want to render a UI that lets the user + select one among several options. + In HTML, we would use a radio button input, something like + <input type="radio" name="choice" value="option1" />. + Each choice is represented as a unique input element. + This never struck me as ideal. + Most of the time, radio buttons are grouped together to affect the same name. + The HTML approach leads to a lot of boilerplate (duplication of type="radio" + and name="choice" for each choice). + Also, unlike radio buttons on desktop, mobile OSes don’t provide a strong standard UI + for selecting one option. + Most mobile apps use richer, custom UIs for these interactions. + So in HXML, we implement this UI using an element called <select-single>: +

+
+
+
Select-single element
+
+
<select-single name="choice"> (1)
   <option value="option1"> (2)
     <text>Option 1</text> (3)
   </option>
@@ -13976,72 +20113,90 @@ So in HXML, we implement this UI using an element called <select-single
     <text>Option 2</text>
   </option>
 </select-single>
-
-
-
-
    -
  1. -

    Element representing an input where a single choice is selected. The name of the selection is defined once here.

    -
  2. -
  3. -

    Element representing one of the choices. The choice value is defined here.

    -
  4. -
  5. -

    The UI of the selection. In this example, we use text, but we can use any UI elements.

    -
  6. -
-
-
-

The <select-single> element is the parent of the input for selecting one choice out of many. -This element contains the name attribute used when serializing the selected choice. -<option> elements within <select-single> represent the available choices. -Note that each <option> element has a value attribute. -When pressed, this will be the selected value of the input. -The <option> element can contain any other UI elements within it. -This means that we’re not hampered by rendering the input as a list of radio buttons with labels. -We can render the options as radios, tags, images, or anything else that would be intuitive for our interface. -HXML styling supports modifiers for pressed and selected states, letting us customize the UI to highlight the selected option.

-
-
-

Describing all features of inputs in HXML would take an entire chapter. -Instead, I’ll summarize a few other input elements and their features.

-
-
-
    -
  • -

    <select-multiple> works like <select-single>, but it supports toggling multiple options on & off. This replaces checkbox inputs in HTML.

    -
  • -
  • -

    The <switch> element renders an on/off switch that is common in mobile UIs

    -
  • -
  • -

    The <date-field> element supports entering in specific dates, and comes with a wide range of customizations for formatting, settings ranges, etc.

    -
  • -
-
-
-

Two more things to mention about inputs. -First is the <form> element. -The <form> element is used to group together inputs for serialization. -When a user takes an action that triggers a backend request, the Hyperview client will serialize all inputs in the surrounding <form> and include them in the request. -This is true for both GET and POST requests. -We will cover this in more detail when talking about behaviors later in this chapter. -Also later in this chapter, I’ll talk about support for custom elements in HXML. -With custom elements, you can also create your own input elements. -Custom input elements allow you to build incredible powerful interactions with simple XML syntax that integrates well with the rest of HXML.

-
-
-
-

Styling

-
-

So far, we haven’t mentioned how to apply styling to all of the HXML elements. -We’ve seen from the Hello World app that each <screen> can contain a <styles> element. -Let’s re-visit the Hello World app and fill out the <styles> element.

-
-
-
UI styling example
-
-
<doc xmlns="https://hyperview.org/hyperview">
+                        
+
+
+
    +
  1. +

    Element representing an input where a single choice is selected. The name of the + selection is defined once here.

    +
  2. +
  3. +

    Element representing one of the choices. The choice value is defined here.

    +
  4. +
  5. +

    The UI of the selection. In this example, we use text, but we can use any UI + elements.

    +
  6. +
+
+
+

The <select-single> element is the parent of the input for selecting one + choice out of many. + This element contains the name attribute used when serializing the selected + choice. + <option> elements within <select-single> represent the + available choices. + Note that each <option> element has a value attribute. + When pressed, this will be the selected value of the input. + The <option> element can contain any other UI elements within it. + This means that we’re not hampered by rendering the input as a list of radio buttons + with labels. + We can render the options as radios, tags, images, or anything else that would be intuitive + for our interface. + HXML styling supports modifiers for pressed and selected states, letting us customize the UI + to highlight the selected option. +

+
+
+

Describing all features of inputs in HXML would take an entire chapter. + Instead, I’ll summarize a few other input elements and their features.

+
+
+
    +
  • +

    <select-multiple> works like <select-single>, + but it supports toggling multiple options on & off. This replaces checkbox + inputs in HTML.

    +
  • +
  • +

    The <switch> element renders an on/off switch that is common in + mobile UIs

    +
  • +
  • +

    The <date-field> element supports entering in specific dates, and + comes with a wide range of customizations for formatting, settings ranges, etc.

    +
  • +
+
+
+

Two more things to mention about inputs. + First is the <form> element. + The <form> element is used to group together inputs for serialization. + When a user takes an action that triggers a backend request, the Hyperview client will + serialize all inputs in the surrounding <form> and include them in the + request. + This is true for both GET and POST requests. + We will cover this in more detail when talking about behaviors later in this chapter. + Also later in this chapter, I’ll talk about support for custom elements in HXML. + With custom elements, you can also create your own input elements. + Custom input elements allow you to build incredible powerful interactions with simple XML + syntax that integrates well with the rest of HXML.

+
+
+
+

Styling

+
+

So far, we haven’t mentioned how to apply styling to all of the HXML elements. + We’ve seen from the Hello World app that each <screen> can contain + a <styles> element. + Let’s re-visit the Hello World app and fill out the <styles> + element.

+
+
+
UI styling example
+
+
<doc xmlns="https://hyperview.org/hyperview">
   <screen>
     <styles> (1)
       <style class="body" flex="1" flexDirection="column" /> (2)
@@ -14061,75 +20216,92 @@ Let’s re-visit the Hello World app and fill out the <styles>
-
-
-
-
    -
  1. -

    Element encapsulating all of the styling for the screen

    -
  2. -
  3. -

    Example of a definition of a style class for “body”

    -
  4. -
  5. -

    Applying the “body” style class to a UI element

    -
  6. -
  7. -

    Example of applying multiple style classes (h1 and info) to an element

    -
  8. -
-
-
-

You’ll note that in HXML, styling is part of the XML format, rather than using a separate language like CSS. -However, we can draw some parallels between CSS rules and the <style> element. -A CSS rule consists of a selector and declarations. -In the current version of HXML, the only available selector is a class name, indicated by the class attribute. -The rest of the attributes on the <style> element are declarations, consisting of properties and property values.

-
-
-

UI elements within the <screen> can reference the <style> rules by adding the class names to their <style> property. -Note the <text> element around “Hello World!” references two style classes: h1 and info. The styles from the corresponding classes are merged together in the order they appear on the element. -It’s worth noting that styling properties are similar to those in CSS (color, margins/padding, borders, etc). -Currently, the only available layout engine is based on flexbox.

-
-
-

Style rules can get quite verbose. -For the sake of brevity, we won’t include the <styles> element in the rest of the examples in this chapter unless necessary.

-
-
-
-

Custom elements

-
-

The core UI elements that ship with Hyperview are quite basic. -Most mobile apps require richer elements to deliver a great user experience. -Luckily, HXML can easily accommodate custom elements in its syntax. -This is because HXML is really just XML, aka “Extensible Markup Language”. -Extensibility is already built into the format! -Developers are free to define new elements and attributes to represent custom elements.

-
-
-

Let’s see this in action with a concrete example. -Assume that we want to add a map element to our Hello World app. -We want the map to display a defined area, and one or more markers at specific coordinates in that area. -Let’s translate these requirements into XML:

-
-
-
    -
  • -

    An <area> element will represent the area displayed by the map. To specify the area, the element will include attributes for latitude and longitude for the center of the area, and a latitude-delta and longitude-delta indicating the +/- display area around the center.

    -
  • -
  • -

    A <marker> element will represent a marker in the area. The coordinates of the marker will be defined by latitude and longitude attributes on the marker.

    -
  • -
-
-
-

Using these custom XML elements, an instance of the map in our app might look like this:

-
-
-
Custom elements in HXML
-
-
<doc xmlns="https://hyperview.org/hyperview">
+                        
+
+
+
    +
  1. +

    Element encapsulating all of the styling for the screen

    +
  2. +
  3. +

    Example of a definition of a style class for “body”

    +
  4. +
  5. +

    Applying the “body” style class to a UI element

    +
  6. +
  7. +

    Example of applying multiple style classes (h1 and info) to an element

    +
  8. +
+
+
+

You’ll note that in HXML, styling is part of the XML format, rather than using a + separate language like CSS. + However, we can draw some parallels between CSS rules and the <style> + element. + A CSS rule consists of a selector and declarations. + In the current version of HXML, the only available selector is a class name, indicated by + the class attribute. + The rest of the attributes on the <style> element are declarations, + consisting of properties and property values.

+
+
+

UI elements within the <screen> can reference the + <style> rules by adding the class names to their + <style> property. + Note the <text> element around “Hello World!” references two + style classes: h1 and info. The styles from the corresponding + classes are merged together in the order they appear on the element. + It’s worth noting that styling properties are similar to those in CSS (color, + margins/padding, borders, etc). + Currently, the only available layout engine is based on flexbox.

+
+
+

Style rules can get quite verbose. + For the sake of brevity, we won’t include the <styles> element in + the rest of the examples in this chapter unless necessary.

+
+
+
+

Custom elements

+
+

The core UI elements that ship with Hyperview are quite basic. + Most mobile apps require richer elements to deliver a great user experience. + Luckily, HXML can easily accommodate custom elements in its syntax. + This is because HXML is really just XML, aka “Extensible Markup Language”. + Extensibility is already built into the format! + Developers are free to define new elements and attributes to represent custom elements.

+
+
+

Let’s see this in action with a concrete example. + Assume that we want to add a map element to our Hello World app. + We want the map to display a defined area, and one or more markers at specific coordinates + in that area. + Let’s translate these requirements into XML:

+
+
+
    +
  • +

    An <area> element will represent the area displayed by the map. To + specify the area, the element will include attributes for latitude and + longitude for the center of the area, and a latitude-delta + and longitude-delta indicating the +/- display area around the center. +

    +
  • +
  • +

    A <marker> element will represent a marker in the area. The + coordinates of the marker will be defined by latitude and + longitude attributes on the marker.

    +
  • +
+
+
+

Using these custom XML elements, an instance of the map in our app might look like this:

+
+
+
Custom elements in HXML
+
+
<doc xmlns="https://hyperview.org/hyperview">
   <screen>
     <body>
       <view>
@@ -14141,47 +20313,59 @@ Let’s translate these requirements into XML:

</body> </screen> </doc>
-
-
-
-
    -
  1. -

    Custom element representing the area rendered by the map

    -
  2. -
  3. -

    Custom element representing a marker rendered at specific coordinates on the map

    -
  4. -
-
-
-

The syntax feels right at home among the core HXML elements. -However, there’s a potential problem. -“area” and “marker” are pretty generic names. -I could see <area> and <marker> elements being used by a customization to render charts & graphs. -If our app renders both maps and charts, the HXML markup would be ambiguous. -What should the client render when it sees <area> or <marker>?

-
-
-

This is where XML namespaces come in. XML namespaces eliminate ambiguity and collisions between elements and attributes used to represent different things. -Remember that the <doc> element declares that https://hyperview.org/hyperview is the default namespace for the entire document. -Since no other elements define namespaces, every element in the example above is part of the https://hyperview.org/hyperview namespace.

-
-
-

Let’s define a new namespace for our map elements. Since this namespace will not be the default for the document, we also need to assign the namespace to a prefix we will add to our elements:

-
-
-

<doc xmlns="https://hyperview.org/hyperview" xmlns:map="https://mycompany.com/hyperview-map">

-
-
-

This new attribute declares that the map: prefix is associated with the namespace "https://mycompany.com/hyperview-map". -This namespace could be anything, but remember the goal is to use something unique that won’t have collisions. -Using your company/app domain is a good way to guarantee uniqueness. -Now that we have a namespace and prefix, we need to use it for our elements:

-
-
-
Namespacing the custom elements
-
-
<doc xmlns="https://hyperview.org/hyperview" xmlns:map="https://mycompany.com/hyperview-map"> (1)
+                        
+
+
+
    +
  1. +

    Custom element representing the area rendered by the map

    +
  2. +
  3. +

    Custom element representing a marker rendered at specific coordinates on the map

    +
  4. +
+
+
+

The syntax feels right at home among the core HXML elements. + However, there’s a potential problem. + “area” and “marker” are pretty generic names. + I could see <area> and <marker> elements being used by + a customization to render charts & graphs. + If our app renders both maps and charts, the HXML markup would be ambiguous. + What should the client render when it sees <area> or + <marker>?

+
+
+

This is where XML namespaces come in. XML namespaces eliminate ambiguity and collisions + between elements and attributes used to represent different things. + Remember that the <doc> element declares that + https://hyperview.org/hyperview + is the default namespace for the entire document. + Since no other elements define namespaces, every element in the example above is part of the + https://hyperview.org/hyperview + namespace.

+
+
+

Let’s define a new namespace for our map elements. Since this namespace will not be the + default for the document, we also need to assign the namespace to a prefix we will add to + our elements:

+
+
+

<doc xmlns="https://hyperview.org/hyperview" xmlns:map="https://mycompany.com/hyperview-map"> +

+
+
+

This new attribute declares that the map: prefix is associated with the + namespace "https://mycompany.com/hyperview-map". + This namespace could be anything, but remember the goal is to use something unique that + won’t have collisions. + Using your company/app domain is a good way to guarantee uniqueness. + Now that we have a namespace and prefix, we need to use it for our elements:

+
+
+
Namespacing the custom elements
+
+
<doc xmlns="https://hyperview.org/hyperview" xmlns:map="https://mycompany.com/hyperview-map"> (1)
   <screen>
     <body>
       <view>
@@ -14193,88 +20377,105 @@ Now that we have a namespace and prefix, we need to use it for our elements:

</body> </screen> </doc>
-
-
-
-
    -
  1. -

    Definition of namespace aliased to “map”

    -
  2. -
  3. -

    Adding the namespace to the “area” start tag

    -
  4. -
  5. -

    Adding the namespace to the “marker” self-closing tag

    -
  6. -
  7. -

    Adding the namespace to the “area” end tag

    -
  8. -
-
-
-

That’s it! If we introduced a custom charting library with “area” and “marker” elements, we would create a unique namespace for those elements as well. Within the HXML doc, we could easily disambiguate <map:area> from <chart:area>.

-
-
-

At this point you might be wondering, “how does the Hyperview client know to render a map when my doc includes <map:area>?” -It’s true, so far we only defined the custom element format, but we haven’t implemented the element as a feature in our app. -We will get into the details of implementing custom elements in the next chapter.

-
-
-
-

Behaviors

-
-

As discussed in earlier chapters, HTML supports two basic types of interactions:

-
-
-
    -
  • -

    Clicking a hyperlink: the client will make a GET request and render the response as a new web page.

    -
  • -
  • -

    Submitting a form: the client will (typically) make a POST request with the serialized content of the form, and render the response as a new web page.

    -
  • -
-
-
-

Clicking hyperlinks and submitting forms is enough to build simple web applications. -But relying on just these two interactions limits our ability to build richer UIs. -What if we want something to happen when the user mouses over a certain element, or perhaps when they scroll some content into the viewport? -We can’t do that with basic HTML. -Additionally, both clicks and form submits result in loading a full new web page. -What if we only want to update a small part of the current page? -This is a very common scenario in rich web applications, where users expect to fetch and update content without navigating to a new page.

-
-
-

So with basic HTML, interactions (clicks and submits) are limited and tightly coupled to a single action (loading a new page). -Of course, using JavaScript, we can extend HTML and add some new syntax to support our desired interactions. -htmx (and Intercooler before it) do exactly that with a new set of attributes:

-
-
-
    -
  • -

    Interactions can be added to any element, not just links and forms.

    -
  • -
  • -

    The interaction can be triggered via a click, submit, mouseover, or any other JavaScript event.

    -
  • -
  • -

    The actions resulting from the trigger can modify the current page, not just request a new page.

    -
  • -
-
-
-

By decoupling elements, triggers, and actions, htmx allows us to build rich Hypermedia-driven applications in a way that feels very compatible with HTML syntax and server-side web development.

-
-
-

HXML takes the idea of defining interactions via triggers & actions and builds them into the spec. -We call these interactions “behaviors”. -We use a special <behavior> element to define them. -Here’s an example of a simple behavior that pushes a new mobile screen onto the navigation stack:

-
-
-
Basic behavior
-
-
<text>
+                        
+
+
+
    +
  1. +

    Definition of namespace aliased to “map”

    +
  2. +
  3. +

    Adding the namespace to the “area” start tag

    +
  4. +
  5. +

    Adding the namespace to the “marker” self-closing tag

    +
  6. +
  7. +

    Adding the namespace to the “area” end tag

    +
  8. +
+
+
+

That’s it! If we introduced a custom charting library with “area” and + “marker” elements, we would create a unique namespace for those elements as + well. Within the HXML doc, we could easily disambiguate <map:area> from + <chart:area>.

+
+
+

At this point you might be wondering, “how does the Hyperview client know to render a + map when my doc includes <map:area>?” + It’s true, so far we only defined the custom element format, but we haven’t + implemented the element as a feature in our app. + We will get into the details of implementing custom elements in the next chapter.

+
+
+
+

Behaviors

+
+

As discussed in earlier chapters, HTML supports two basic types of interactions:

+
+
+
    +
  • +

    Clicking a hyperlink: the client will make a GET request and render the response as a + new web page.

    +
  • +
  • +

    Submitting a form: the client will (typically) make a POST request with the + serialized content of the form, and render the response as a new web page.

    +
  • +
+
+
+

Clicking hyperlinks and submitting forms is enough to build simple web applications. + But relying on just these two interactions limits our ability to build richer UIs. + What if we want something to happen when the user mouses over a certain element, or perhaps + when they scroll some content into the viewport? + We can’t do that with basic HTML. + Additionally, both clicks and form submits result in loading a full new web page. + What if we only want to update a small part of the current page? + This is a very common scenario in rich web applications, where users expect to fetch and + update content without navigating to a new page.

+
+
+

So with basic HTML, interactions (clicks and submits) are limited and tightly coupled to a + single action (loading a new page). + Of course, using JavaScript, we can extend HTML and add some new syntax to support our + desired interactions. + htmx (and Intercooler before it) do exactly that with a new set of attributes:

+
+
+
    +
  • +

    Interactions can be added to any element, not just links and forms.

    +
  • +
  • +

    The interaction can be triggered via a click, submit, mouseover, or any other + JavaScript event.

    +
  • +
  • +

    The actions resulting from the trigger can modify the current page, not just request + a new page.

    +
  • +
+
+
+

By decoupling elements, triggers, and actions, htmx allows us to build rich Hypermedia-driven + applications in a way that feels very compatible with HTML syntax and server-side web + development.

+
+
+

HXML takes the idea of defining interactions via triggers & actions and builds them into + the spec. + We call these interactions “behaviors”. + We use a special <behavior> element to define them. + Here’s an example of a simple behavior that pushes a new mobile screen onto the + navigation stack:

+
+
+
Basic behavior
+
+
<text>
   <behavior (1)
     trigger="press" (2)
     action="push" (3)
@@ -14282,170 +20483,205 @@ Here’s an example of a simple behavior that pushes a new mobile screen ont
   />
   Press me!
 </text>
-
-
-
-
    -
  1. -

    The element encapsulating an interaction on the parent <text> element.

    -
  2. -
  3. -

    The trigger that will execute the interaction, in this case pressing the <text> element.

    -
  4. -
  5. -

    The action that will execute when triggered, in this case pushing a new screen onto the current stack.

    -
  6. -
  7. -

    The href to load on the new screen.

    -
  8. -
-
-
-

Let’s break down what’s happening in this example. -First, we have a <text> element with the content "Press me!". -We’ve shown <text> elements before in examples of HXML, so this is nothing new. -But now, the <text> element contains a new child element, <behavior>. -This <behavior> element defines an interaction on the parent <text> element. -It contains two attributes that are required for any behavior:

-
-
-
    -
  • -

    trigger: defines the user action that triggers the behavior

    -
  • -
  • -

    action: defines what happens when triggered

    -
  • -
-
-
-

In this example, the trigger is set to press, meaning this interaction will happen when the user presses the <text> element. -The action attribute is set to push. -push is an action that will push a new screen onto the navigation stack. -Finally, Hyperview needs to know what content to load on the newly pushed screen. -This is where the href attribute comes in. -Notice we don’t need to define the full URL. -Much like in HTML, the href can be an absolute or relative URL.

-
-
-

So that’s a first example of behaviors in HXML. -You may be thinking this syntax seems quite verbose. -Indeed, pressing elements to navigate to a new screen is one of the most common interactions in a mobile app. -It would be nice to have a simpler syntax for the common case. -Luckily, trigger and action attributes have default values of press and push, respectively. -Therefore, they can be ommitted to clean up the syntax:

-
-
-
Basic behavior with defaults
-
-
<text>
+                        
+
+
+
    +
  1. +

    The element encapsulating an interaction on the parent <text> + element.

    +
  2. +
  3. +

    The trigger that will execute the interaction, in this case pressing the + <text> element.

    +
  4. +
  5. +

    The action that will execute when triggered, in this case pushing a new screen onto + the current stack.

    +
  6. +
  7. +

    The href to load on the new screen.

    +
  8. +
+
+
+

Let’s break down what’s happening in this example. + First, we have a <text> element with the content "Press me!". + We’ve shown <text> elements before in examples of HXML, so this is + nothing new. + But now, the <text> element contains a new child element, + <behavior>. + This <behavior> element defines an interaction on the parent + <text> element. + It contains two attributes that are required for any behavior:

+
+
+
    +
  • +

    trigger: defines the user action that triggers the behavior

    +
  • +
  • +

    action: defines what happens when triggered

    +
  • +
+
+
+

In this example, the trigger is set to press, meaning this + interaction will happen when the user presses the <text> element. + The action attribute is set to push. + push is an action that will push a new screen onto the navigation stack. + Finally, Hyperview needs to know what content to load on the newly pushed screen. + This is where the href attribute comes in. + Notice we don’t need to define the full URL. + Much like in HTML, the href can be an absolute or relative URL. +

+
+
+

So that’s a first example of behaviors in HXML. + You may be thinking this syntax seems quite verbose. + Indeed, pressing elements to navigate to a new screen is one of the most common interactions + in a mobile app. + It would be nice to have a simpler syntax for the common case. + Luckily, trigger and action attributes have default values of + press and push, respectively. + Therefore, they can be ommitted to clean up the syntax:

+
+
+
Basic behavior with defaults
+
+
<text>
   <behavior href="/next-screen" /> (1)
   Press me!
 </text>
-
-
-
-
    -
  1. -

    When pressed, this behavior will open a new screen with the given URL.

    -
  2. -
-
-
-

This markup for the <behavior> will produce the same interaction as the earlier example. -With the default attributes, the <behavior> element looks similar to an anchor <a> in HTML. -But the full syntax achieves our goals of decoupling elements, triggers, and actions:

-
-
-
    -
  • -

    Behaviors can be added to any element, they are not limited to links and forms.

    -
  • -
  • -

    Behaviors can specify an explicit trigger, not just clicks or form submits.

    -
  • -
  • -

    Behaviors can specify an explicit action, not just a request for a new page.

    -
  • -
  • -

    Extra attributes like href provide more context for the action.

    -
  • -
-
-
-

Additionally, using a dedicated <behavior> element means a single element can define multiple behaviors. -This lets us execute several actions from the same trigger. -Or, we can execute different actions for different triggers on the same element. -We will show examples of the power of multiple behaviors at the end of this chapter. -First we need to show the variety of supported actions and triggers.

-
-
-
Actions
-
-

Behavior actions in Hyperview fall into four general categories:

-
-
-
    -
  • -

    Navigation actions, which load new screens and move between them

    -
  • -
  • -

    Update actions, which modify the HXML of the current screen

    -
  • -
  • -

    System actions, which interact with OS-level capabilities.

    -
  • -
  • -

    Custom actions, which can execute any code you add to the client.

    -
  • -
-
-
-
Navigation Actions
-
-

We’ve already seen the simplest type of action, push. -We classify push as a “navigation action”, since it’s related to navigating screens in the mobile app. -Pushing a screen onto the navigation stack is just one of several navigation actions supported in Hyperview. -Users also need to be able to go back to previous screens, open and close modals, switch between tabs, or jump to arbitrary screens. -Each of these types of navigations is supported through a different value for the action attribute:

-
-
-
    -
  • -

    push: Push a new screen into the current navigation stack. This looks like a screen sliding in from the right, on top of the current screen.

    -
  • -
  • -

    new: Open a new navigation stack as a modal. This looks like a screen sliding in from the bottom, on top of the current screen.

    -
  • -
  • -

    back: This is a complement to the push action. It pops the current screen off of the navigation stack (sliding it to the right).

    -
  • -
  • -

    close: This is a complement to the new action. It closes the current navigation stack (sliding it down).

    -
  • -
  • -

    reload: Similar to a browser’s “refresh” button, this will re-request the content of the current screen.

    -
  • -
  • -

    navigate: This action will attempt to find a screen with the given href already loaded in the app. If the screen exists, the app will jump to that screen. If it doesn’t exist, it will act the same as push.

    -
  • -
-
-
-

push, new, and navigate all load a new screen. -Thus, they require an href attribute so that Hyperview knows what content to request for the new screen. -back and close do not load new screens, and thus do not require the href attribute. -reload is an interesting case. -By default, it will use the URL of the screen when re-requesting the content for the screen. -However, if you want to replace the screen with a different one, you can provide an href attribute with reload on the behavior element.

-
-
-

Let’s look at an example “widgets” app that uses several navigation actions on one screen:

-
-
-
Navigation action examples
-
-
<screen>
+                        
+
+
+
    +
  1. +

    When pressed, this behavior will open a new screen with the given URL.

    +
  2. +
+
+
+

This markup for the <behavior> will produce the same interaction as the + earlier example. + With the default attributes, the <behavior> element looks similar to an + anchor <a> in HTML. + But the full syntax achieves our goals of decoupling elements, triggers, and actions:

+
+
+
    +
  • +

    Behaviors can be added to any element, they are not limited to links and forms.

    +
  • +
  • +

    Behaviors can specify an explicit trigger, not just clicks or form + submits.

    +
  • +
  • +

    Behaviors can specify an explicit action, not just a request for a new + page.

    +
  • +
  • +

    Extra attributes like href provide more context for the action.

    +
  • +
+
+
+

Additionally, using a dedicated <behavior> element means a single element + can define multiple behaviors. + This lets us execute several actions from the same trigger. + Or, we can execute different actions for different triggers on the same element. + We will show examples of the power of multiple behaviors at the end of this chapter. + First we need to show the variety of supported actions and triggers.

+
+
+
Actions
+
+

Behavior actions in Hyperview fall into four general categories:

+
+
+
    +
  • +

    Navigation actions, which load new screens and move between them

    +
  • +
  • +

    Update actions, which modify the HXML of the current screen

    +
  • +
  • +

    System actions, which interact with OS-level capabilities.

    +
  • +
  • +

    Custom actions, which can execute any code you add to the client.

    +
  • +
+
+
+
Navigation Actions
+
+

We’ve already seen the simplest type of action, push. + We classify push as a “navigation action”, since it’s + related to navigating screens in the mobile app. + Pushing a screen onto the navigation stack is just one of several navigation actions + supported in Hyperview. + Users also need to be able to go back to previous screens, open and close modals, + switch between tabs, or jump to arbitrary screens. + Each of these types of navigations is supported through a different value for the + action attribute:

+
+
+
    +
  • +

    push: Push a new screen into the current navigation stack. This + looks like a screen sliding in from the right, on top of the current screen. +

    +
  • +
  • +

    new: Open a new navigation stack as a modal. This looks like a + screen sliding in from the bottom, on top of the current screen.

    +
  • +
  • +

    back: This is a complement to the push action. It + pops the current screen off of the navigation stack (sliding it to the + right).

    +
  • +
  • +

    close: This is a complement to the new action. It + closes the current navigation stack (sliding it down).

    +
  • +
  • +

    reload: Similar to a browser’s “refresh” + button, this will re-request the content of the current screen.

    +
  • +
  • +

    navigate: This action will attempt to find a screen with the + given href already loaded in the app. If the screen exists, the + app will jump to that screen. If it doesn’t exist, it will act the + same as push.

    +
  • +
+
+
+

push, new, and navigate all load a new screen. + Thus, they require an href attribute so that Hyperview knows what + content to request for the new screen. + back and close do not load new screens, and thus do not + require the href attribute. + reload is an interesting case. + By default, it will use the URL of the screen when re-requesting the content for the + screen. + However, if you want to replace the screen with a different one, you can provide an + href attribute with reload on the behavior element. +

+
+
+

Let’s look at an example “widgets” app that uses several navigation + actions on one screen:

+
+
+
Navigation action examples
+
+
<screen>
   <body>
     <header>
       <text>
@@ -14469,82 +20705,108 @@ However, if you want to replace the screen with a different one, you can provide
     </list>
   </body>
 </screen>
-
-
-
-
    -
  1. -

    Takes the user to the previous screen

    -
  2. -
  3. -

    Opens a new modal to add a widget

    -
  4. -
  5. -

    Reloads the content of the screen, showing new widgets from the backend

    -
  6. -
  7. -

    Pushes a new screen with details for a specific widget

    -
  8. -
-
-
-

Most screens in your app will need a way for the user to backtrack to the previous screen. -This is usually done with a button in the header that uses either a “back” or “close” action, depending on how the screen was opened. -In this example, we’re assuming the widgets screen was pushed onto the navigation stack, so the “back” action is appropriate. -The header contains a second button that allows the user to enter data for a new widget. -Pressing this button will open a modal with a “New Widget” screen. -Since this “New Widget” screen will open as a modal, it will need a corresponding “close” action to dismiss itself and show our “widgets” screen again. -Finally, to see more details about a specific widget, each <item> element contains a behavior with a “push” action. -This action will push a “Widget Detail” screen onto the current navigation stack. -Like in the “Widgets” screen, “Widget Detail” will need a button in the header that uses the “back” action to let the user backtrack.

-
-
-

On the web, the browser handles basic navigation needs such as going back/forward, reloading the current page, or jumping to a bookmark. -iOS and Android don’t provide this sort of universal navigation for native mobile apps. -It’s on the app developers to handle this themselves. -Navigation actions in HXML provide an easy but powerful way for developers to build an architecture that makes sense for their app.

-
-
-
-
Update Actions
-
-

Behavior actions are not just limited to navigating between screens. -They can also be used to change the content on the current screen. -We call these “update actions”. -Much like navigation actions, update actions make a request to the backend. -However, the response is not an entire HXML document, but a fragment of HXML. -This fragment is added to the HXML of the current screen, resulting in an update to the UI. -The action attribute of the <behavior> determines how the fragment gets incorporated into the HXML. -We also need to introduce a new target attribute on <behavior> to define where the fragment gets incorporated in the existing doc. -The target attribute is an ID reference to an existing element on the screen.

-
-
-

Hyperview currently supports these update actions, representing different ways to incorporate the fragment into the screen:

-
-
-
    -
  • -

    replace: replaces the entire target element with the fragment

    -
  • -
  • -

    replace-inner: replaces the children of the target element with the fragment

    -
  • -
  • -

    append: adds the fragment after the last child of the target element

    -
  • -
  • -

    prepend: adds the fragment before the first child of the target element.

    -
  • -
-
-
-

Let’s look at some examples to make this more concrete. -For these examples, let’s assume our backend accepts GET requests to /fragment, and the response is a fragment of HXML that looks like <text>My fragment</text>.

-
-
-
Update action examples
-
-
<screen>
+                                
+
+
+
    +
  1. +

    Takes the user to the previous screen

    +
  2. +
  3. +

    Opens a new modal to add a widget

    +
  4. +
  5. +

    Reloads the content of the screen, showing new widgets from the backend

    +
  6. +
  7. +

    Pushes a new screen with details for a specific widget

    +
  8. +
+
+
+

Most screens in your app will need a way for the user to backtrack to the previous + screen. + This is usually done with a button in the header that uses either a + “back” or “close” action, depending on how the screen was + opened. + In this example, we’re assuming the widgets screen was pushed onto the + navigation stack, so the “back” action is appropriate. + The header contains a second button that allows the user to enter data for a new + widget. + Pressing this button will open a modal with a “New Widget” screen. + Since this “New Widget” screen will open as a modal, it will need a + corresponding “close” action to dismiss itself and show our + “widgets” screen again. + Finally, to see more details about a specific widget, each <item> + element contains a behavior with a “push” action. + This action will push a “Widget Detail” screen onto the current + navigation stack. + Like in the “Widgets” screen, “Widget Detail” will need a + button in the header that uses the “back” action to let the user + backtrack.

+
+
+

On the web, the browser handles basic navigation needs such as going back/forward, + reloading the current page, or jumping to a bookmark. + iOS and Android don’t provide this sort of universal navigation for native + mobile apps. + It’s on the app developers to handle this themselves. + Navigation actions in HXML provide an easy but powerful way for developers to build + an architecture that makes sense for their app.

+
+
+
+
Update Actions
+
+

Behavior actions are not just limited to navigating between screens. + They can also be used to change the content on the current screen. + We call these “update actions”. + Much like navigation actions, update actions make a request to the backend. + However, the response is not an entire HXML document, but a fragment of HXML. + This fragment is added to the HXML of the current screen, resulting in an update to + the UI. + The action attribute of the <behavior> determines + how the fragment gets incorporated into the HXML. + We also need to introduce a new target attribute on + <behavior> to define where the fragment gets incorporated in the + existing doc. + The target attribute is an ID reference to an existing element on the + screen.

+
+
+

Hyperview currently supports these update actions, representing different ways to + incorporate the fragment into the screen:

+
+
+
    +
  • +

    replace: replaces the entire target element with the fragment +

    +
  • +
  • +

    replace-inner: replaces the children of the target element with + the fragment

    +
  • +
  • +

    append: adds the fragment after the last child of the target + element

    +
  • +
  • +

    prepend: adds the fragment before the first child of the target + element.

    +
  • +
+
+
+

Let’s look at some examples to make this more concrete. + For these examples, let’s assume our backend accepts GET requests + to /fragment, and the response is a fragment of HXML that looks like + <text>My fragment</text>.

+
+
+
Update action examples
+
+
<screen>
   <body>
     <text>
       <behavior action="replace" href="/fragment" target="area1" /> (1)
@@ -14580,53 +20842,66 @@ For these examples, let’s assume our backend accepts GET requ
 
   </body>
 </screen>
-
-
-
-
    -
  1. -

    Replaces the area1 element with fetched fragment

    -
  2. -
  3. -

    Replaces the child elements of area2 with fetched fragment

    -
  4. -
  5. -

    Appends the fetched fragment to area3

    -
  6. -
  7. -

    Prepends the fetched fragment to area4

    -
  8. -
-
-
-

In this example, we have a screen with four buttons corresponding to the four update actions: replace, replace-inner, append, prepend. -Below each button, there’s a corresponding <view> containing some text. -Note that the id of each view matches the target on the behaviors of the corresponding button.

-
-
-

When the user presses the first button, the Hyperview client makes a request for /fragment. -Next, it looks for the target, ie the element with id “area1”. -Finally, it replaces the <view id="area1"> element with the fetched fragment, <text>My fragment</text>. -The existing view and text contained in that view will be replaced. -To the user, it will look like “Existing content” was changed to “My fragment”. -In the HXML, the element <view id="area1"> will also be gone.

-
-
-

The second button behaves in a similar way to the first one. -However, the replace-inner action does not remove the target element from the screen, it only replaces the children. -This means the resulting markup will look like <view id="area2"><text>My fragment</text></view>.

-
-
-

The third and fourth buttons do not remove any content from the screen. -Instead, the fragment will be added either after (in the case of append) or before (prepend) the children of the target element.

-
-
-

For completeness, let’s look at the state of the screen after a user presses all four buttons:

-
-
-
Update actions, after pressing buttons
-
-
<screen>
+                                
+
+
+
    +
  1. +

    Replaces the area1 element with fetched fragment

    +
  2. +
  3. +

    Replaces the child elements of area2 with fetched fragment

    +
  4. +
  5. +

    Appends the fetched fragment to area3

    +
  6. +
  7. +

    Prepends the fetched fragment to area4

    +
  8. +
+
+
+

In this example, we have a screen with four buttons corresponding to the four update + actions: replace, replace-inner, append, + prepend. + Below each button, there’s a corresponding <view> + containing some text. + Note that the id of each view matches the target on the + behaviors of the corresponding button.

+
+
+

When the user presses the first button, the Hyperview client makes a request for + /fragment. + Next, it looks for the target, ie the element with id “area1”. + Finally, it replaces the <view id="area1"> element with the + fetched fragment, <text>My fragment</text>. + The existing view and text contained in that view will be replaced. + To the user, it will look like “Existing content” was changed to + “My fragment”. + In the HXML, the element <view id="area1"> will also be gone.

+
+
+

The second button behaves in a similar way to the first one. + However, the replace-inner action does not remove the target element + from the screen, it only replaces the children. + This means the resulting markup will look like + <view id="area2"><text>My fragment</text></view>. +

+
+
+

The third and fourth buttons do not remove any content from the screen. + Instead, the fragment will be added either after (in the case of + append) or before (prepend) the children of the target + element.

+
+
+

For completeness, let’s look at the state of the screen after a user presses + all four buttons:

+
+
+
Update actions, after pressing buttons
+
+
<screen>
   <body>
     <text>
       <behavior action="replace" href="/fragment" target="area1" />
@@ -14662,43 +20937,55 @@ Instead, the fragment will be added either after (in the case of append
-
-
-
-
    -
  1. -

    Fragment completely replaced the target using replace action

    -
  2. -
  3. -

    Fragment replaced the children of the target using replace-inner action

    -
  4. -
  5. -

    Fragment added as last child of the target using append action

    -
  6. -
  7. -

    fragment added as the first child of the target using prepend action

    -
  8. -
-
-
-

The examples above show actions making GET requests to the backend. -But these actions can also make POST requests by setting verb="post" on the <behavior> element. -For both GET and POST requests, the data from the parent <form> element will be serialized and included in the request. -For GET requests, the content will be URL-encoded and added as query params. -For POST requests, the content will be form-URL encoded and set on the request body. -Since they support POST and form data, update actions are often used to send data to the backend.

-
-
-

So far, our example of update actions require getting new content from the backend and adding it to the screen. -But sometimes we just want to change the state of existing elements. -The most common state to change for an element is its visibility. -Hyperview has hide, show, and toggle actions that do just that. -Like the other update actions, hide, show, and toggle use the target attribute to apply the action to an element on the current screen.

-
-
-
Show, hide, and toggle actions
-
-
<screen>
+                                
+
+
+
    +
  1. +

    Fragment completely replaced the target using replace action

    +
  2. +
  3. +

    Fragment replaced the children of the target using replace-inner + action

    +
  4. +
  5. +

    Fragment added as last child of the target using append action +

    +
  6. +
  7. +

    fragment added as the first child of the target using prepend + action

    +
  8. +
+
+
+

The examples above show actions making GET requests to the backend. + But these actions can also make POST requests by setting + verb="post" on the <behavior> element. + For both GET and POST requests, the data from the parent + <form> element will be serialized and included in the request. + For GET requests, the content will be URL-encoded and added as query + params. + For POST requests, the content will be form-URL encoded and set on the + request body. + Since they support POST and form data, update actions are often used to + send data to the backend.

+
+
+

So far, our example of update actions require getting new content from the backend + and adding it to the screen. + But sometimes we just want to change the state of existing elements. + The most common state to change for an element is its visibility. + Hyperview has hide, show, and toggle actions + that do just that. + Like the other update actions, hide, show, and + toggle use the target attribute to apply the action to an + element on the current screen.

+
+
+
Show, hide, and toggle actions
+
+
<screen>
   <body>
     <text>
       <behavior action="hide" target="area" /> (1)
@@ -14720,105 +21007,121 @@ Like the other update actions, hide, show, and t
     </view>
   </body>
 </screen>
-
-
-
-
    -
  1. -

    Hides the element with id “area”.

    -
  2. -
  3. -

    Shows the element with id “area”.

    -
  4. -
  5. -

    Toggles the visibility of the element with id “area”.

    -
  6. -
  7. -

    The element targeted by the actions.

    -
  8. -
-
-
-

In this example, the three buttons labeled “Hide”, “Show”, and “Toggle” will modify the display state of the <view> with ID “area”. -Pressing “Hide” multiple times will have no affect once the view is hidden. -Likewise, pressing “Show” multiple times will have no affect once the view is showing. -Pressing “Toggle” will keep flipping the visibility status of the element between showing and hidden.

-
-
-

Hyperview comes with other actions that modify the existing HXML. -We won’t cover them in detail, but I’ll mention them briefly here:

-
-
-
    -
  • -

    set-value: this action can set the value of an input element such as <text-field>, <switch>, <select-single>, etc.

    -
  • -
  • -

    select-all and unselect-all work with the <select-multiple> element to select/deselect all options.

    -
  • -
-
-
-
-
System Actions
-
-

Some standard Hyperview actions don’t interact with the HXML at all. -Instead, they expose functionality provided by the mobile OS. -For example, both Android and iOS support a system-level “Share” UI. -This UI allows sharing URLs and messages from one app to another app. -Hyperview has a share action to support this interaction. -It involves a custom namespace, and share-specific attributes.

-
-
-
System Share action
-
-
<behavior
+                                
+
+
+
    +
  1. +

    Hides the element with id “area”.

    +
  2. +
  3. +

    Shows the element with id “area”.

    +
  4. +
  5. +

    Toggles the visibility of the element with id “area”.

    +
  6. +
  7. +

    The element targeted by the actions.

    +
  8. +
+
+
+

In this example, the three buttons labeled “Hide”, “Show”, + and “Toggle” will modify the display state of the + <view> with ID “area”. + Pressing “Hide” multiple times will have no affect once the view is + hidden. + Likewise, pressing “Show” multiple times will have no affect once the + view is showing. + Pressing “Toggle” will keep flipping the visibility status of the + element between showing and hidden.

+
+
+

Hyperview comes with other actions that modify the existing HXML. + We won’t cover them in detail, but I’ll mention them briefly here:

+
+
+
    +
  • +

    set-value: this action can set the value of an input element + such as <text-field>, <switch>, + <select-single>, etc.

    +
  • +
  • +

    select-all and unselect-all work with the + <select-multiple> element to select/deselect all options. +

    +
  • +
+
+
+
+
System Actions
+
+

Some standard Hyperview actions don’t interact with the HXML at all. + Instead, they expose functionality provided by the mobile OS. + For example, both Android and iOS support a system-level “Share” UI. + This UI allows sharing URLs and messages from one app to another app. + Hyperview has a share action to support this interaction. + It involves a custom namespace, and share-specific attributes.

+
+
+
System Share action
+
+
<behavior
   xmlns:share="https://instawork.com/hyperview-share" (1)
   trigger="press"
   action="share" (2)
   share:url="https://www.instawork.com" (3)
   share:message="Check out this website!" (4)
 />
-
-
-
-
    -
  1. -

    Defines the namespace for the share action.

    -
  2. -
  3. -

    The action of this behavior will bring up the share sheet.

    -
  4. -
  5. -

    URL to be shared

    -
  6. -
  7. -

    Message to be shared

    -
  8. -
-
-
-

We’ve seen XML namespaces when talking about custom elements. -Here, we are using a namespace for the url and message attributes on the <behavior>. -These attribute names are generic and likely used by other components and behaviors, so the namespace ensures there will be no ambiguity. -When pressed, the “share” action will trigger. -The values of the url and message attributes will be passed to the system Share UI. -From there, the user will be able to share the URL & message via SMS, email, or other communication apps.

-
-
-

The share action shows how a behavior action can use custom attributes to pass along extra data needed for the interactions. -But some actions require even more structured data. -This can be provided via child elements on the <behavior>. -Hyperview uses this to implement the alert action. -The alert action shows a customized system-level dialog box. -This dialog needs configuration for a title and message, but also for customized buttons. -Each button needs to then trigger another behavior when pressed. -This level of configuration cannot be done with just attributes, so we use custom child elements to represent the behavior of each button.

-
-
-
System alert action
-
-
<behavior
+                                
+
+
+
    +
  1. +

    Defines the namespace for the share action.

    +
  2. +
  3. +

    The action of this behavior will bring up the share sheet.

    +
  4. +
  5. +

    URL to be shared

    +
  6. +
  7. +

    Message to be shared

    +
  8. +
+
+
+

We’ve seen XML namespaces when talking about custom elements. + Here, we are using a namespace for the url and message + attributes on the <behavior>. + These attribute names are generic and likely used by other components and behaviors, + so the namespace ensures there will be no ambiguity. + When pressed, the “share” action will trigger. + The values of the url and message attributes will be + passed to the system Share UI. + From there, the user will be able to share the URL & message via SMS, email, or + other communication apps.

+
+
+

The share action shows how a behavior action can use custom attributes + to pass along extra data needed for the interactions. + But some actions require even more structured data. + This can be provided via child elements on the <behavior>. + Hyperview uses this to implement the alert action. + The alert action shows a customized system-level dialog box. + This dialog needs configuration for a title and message, but also for customized + buttons. + Each button needs to then trigger another behavior when pressed. + This level of configuration cannot be done with just attributes, so we use custom + child elements to represent the behavior of each button.

+
+
+
System alert action
+
+
<behavior
   xmlns:alert="https://hyperview.org/hyperview-alert" (1)
   trigger="press"
   action="alert" (2)
@@ -14830,112 +21133,137 @@ This level of configuration cannot be done with just attributes, so we use custo
   </alert:option>
   <alert:option alert:label="Cancel" /> (7)
 </behavior>
-
-
-
-
    -
  1. -

    Defines the namespace for the alert action.

    -
  2. -
  3. -

    The action of this behavior will bring up a system dialog box.

    -
  4. -
  5. -

    Title of the dialog box.

    -
  6. -
  7. -

    Content of the dialog box.

    -
  8. -
  9. -

    A “continue” option in the dialog box

    -
  10. -
  11. -

    When “continue” is pressed, push a new screen onto the navigation stack.

    -
  12. -
  13. -

    A “cancel” option that dismisses the dialog box.

    -
  14. -
-
-
-

Like the share behavior, alert uses a namespace to define some attributes and elements. -The <behavior> element itself contains the title and message attributes for the dialog box. -The button options for the dialog are defined using a new <option> element nested in the <behavior>. -Notice that each <option> element has a label, and then optionally contains a <behavior> itself! -This structure of the HXML allows the system dialog to trigger any interaction that can be defined as a <behavior>. -In the example above, pressing the “Continue” button will open a new screen. -But we could just as easily trigger an update action to change the current screen. -We could even open a share sheet, or a second dialog box. -But please don’t do that in a real app! -With great power comes great responsibility.

-
-
-
-
Custom Actions
-
-

You can build a lot of mobile UIs with Hyperview’s standard navigation, update, and system actions. -But the standard set may not cover all interactions you will need for your mobile app. -Luckily, the action system is extensible. -In the same way you can add custom elements to Hyperview, you can also add custom behavior actions. -Custom actions have a similar syntax to the share and alert actions, using namespaces for attributes that pass along extra data. -Custom actions also have full access to the HXML of the current screen, so they can modify the state or add/remove elements from the current screen. -In the next chapter, we will create a custom behavior action to enhance our mobile contacts app.

-
-
-
-
-
Triggers
-
-

We’ve already seen the simplest type of trigger, a press on an element. Hyperview supports many other common triggers used in mobile apps.

-
-
-
longPress
-
-

Closely related to a press is a long-press. -A behavior with trigger="longPress" will trigger when the user presses and holds on the element. -“Long-press” interactions are often used for shortcuts and power features. -Sometimes, elements will support different actions for both a press and longPress. -This is done using multiple <behavior> elements on the same UI element.

-
-
-
Long-press trigger example
-
-
<text>
+                                
+
+
+
    +
  1. +

    Defines the namespace for the alert action.

    +
  2. +
  3. +

    The action of this behavior will bring up a system dialog box.

    +
  4. +
  5. +

    Title of the dialog box.

    +
  6. +
  7. +

    Content of the dialog box.

    +
  8. +
  9. +

    A “continue” option in the dialog box

    +
  10. +
  11. +

    When “continue” is pressed, push a new screen onto the navigation + stack.

    +
  12. +
  13. +

    A “cancel” option that dismisses the dialog box.

    +
  14. +
+
+
+

Like the share behavior, alert uses a namespace to define + some attributes and elements. + The <behavior> element itself contains the title and + message attributes for the dialog box. + The button options for the dialog are defined using a new + <option> element nested in the <behavior>. + Notice that each <option> element has a label, and then + optionally contains a <behavior> itself! + This structure of the HXML allows the system dialog to trigger any interaction that + can be defined as a <behavior>. + In the example above, pressing the “Continue” button will open a new + screen. + But we could just as easily trigger an update action to change the current screen. + We could even open a share sheet, or a second dialog box. + But please don’t do that in a real app! + With great power comes great responsibility.

+
+
+
+
Custom Actions
+
+

You can build a lot of mobile UIs with Hyperview’s standard navigation, update, + and system actions. + But the standard set may not cover all interactions you will need for your mobile + app. + Luckily, the action system is extensible. + In the same way you can add custom elements to Hyperview, you can also add custom + behavior actions. + Custom actions have a similar syntax to the share and + alert actions, using namespaces for attributes that pass along extra + data. + Custom actions also have full access to the HXML of the current screen, so they can + modify the state or add/remove elements from the current screen. + In the next chapter, we will create a custom behavior action to enhance our mobile + contacts app.

+
+
+
+
+
Triggers
+
+

We’ve already seen the simplest type of trigger, a press on an + element. Hyperview supports many other common triggers used in mobile apps.

+
+
+
longPress
+
+

Closely related to a press is a long-press. + A behavior with trigger="longPress" will trigger when the user presses + and holds on the element. + “Long-press” interactions are often used for shortcuts and power + features. + Sometimes, elements will support different actions for both a press and + longPress. + This is done using multiple <behavior> elements on the same UI + element.

+
+
+
Long-press trigger example
+
+
<text>
   <behavior trigger="press" action="push" href="/next-screen" /> (1)
   <behavior trigger="longPress" action="push" href="/secret-screen" /> (2)
   Press (or long-press) me!
 </text>
-
-
-
-
    -
  1. -

    Normal press will open the next screen

    -
  2. -
  3. -

    Long press will open a different screen

    -
  4. -
-
-
-

In this example, a normal press will open a new screen and request content from /next-screen. -However, a long press will open a new screen with content from /secret-screen. -This is a contrived example for the sake of brevity. -A better UX would be for the long-press to bring up a contextual menu of shortcuts and advanced options. -This could be achieved by using action="alert" and opening a system dialog box with the shortcuts.

-
-
-
-
load
-
-

Sometimes we want an action to trigger as soon as the screen loads. -trigger="load" does exactly this. -One use case is to quickly load a shell of the screen, and then fill in the main content on the screen with a second update action.

-
-
-
Load trigger example
-
-
<body>
+                                
+
+
+
    +
  1. +

    Normal press will open the next screen

    +
  2. +
  3. +

    Long press will open a different screen

    +
  4. +
+
+
+

In this example, a normal press will open a new screen and request content from + /next-screen. + However, a long press will open a new screen with content from + /secret-screen. + This is a contrived example for the sake of brevity. + A better UX would be for the long-press to bring up a contextual menu of shortcuts + and advanced options. + This could be achieved by using action="alert" and opening a system + dialog box with the shortcuts.

+
+
+
+
load
+
+

Sometimes we want an action to trigger as soon as the screen loads. + trigger="load" does exactly this. + One use case is to quickly load a shell of the screen, and then fill in the main + content on the screen with a second update action. +

+
+
+
Load trigger example
+
+
<body>
   <view>
     <text>My app</text>
     <view id="container"> (1)
@@ -14944,96 +21272,125 @@ One use case is to quickly load a shell of the screen, and then fill in the main
     </view>
   </view>
 </body>
-
-
-
-
    -
  1. -

    Container element without the actual content

    -
  2. -
  3. -

    Behavior that immediately fires off a request for /content to replace the container

    -
  4. -
  5. -

    Loading UI that appears until the content is fetched and replaced.

    -
  6. -
-
-
-

In this example, We load a screen with a heading (“My app”) but no content. -Instead, we show a <view> with ID “container” and some “Loading…​” text. -As soon as this screen loads, the behavior with trigger=“load” fires off the replace action. -It requests content from the /content path and replaces the container view with the response.

-
-
-
-
visible
-
-

Unlike load, the visible trigger will only execute the behavior when the element with the behavior is scrolled into the viewport on the mobile device. -The visible action is commonly used to implement an infinite-scroll interaction on a <list> of <item> elements. -The last item in the list includes a behavior with trigger="visible". -The append action will fetch the next page of items and append them to the list.

-
-
-
-
refresh
-
-

This trigger captures a “pull to refresh” action on <list> and <view> items. -This interaction is associated with fetching up-to-date content from the backend. -Thus, it’s typically paired with an update or reload action to show the latest data on the screen.

-
-
-
Pull-to-refresh trigger example
-
-
<body>
+                                
+
+
+
    +
  1. +

    Container element without the actual content

    +
  2. +
  3. +

    Behavior that immediately fires off a request for /content to replace the + container

    +
  4. +
  5. +

    Loading UI that appears until the content is fetched and replaced.

    +
  6. +
+
+
+

In this example, We load a screen with a heading (“My app”) but no + content. + Instead, we show a <view> with ID “container” and + some “Loading…​” text. + As soon as this screen loads, the behavior with + trigger=“load” fires off the replace action. + It requests content from the /content path and replaces the container + view with the response.

+
+
+
+
visible
+
+

Unlike load, the visible trigger will only execute the + behavior when the element with the behavior is scrolled into the viewport on the + mobile device. + The visible action is commonly used to implement an infinite-scroll + interaction on a <list> of <item> elements. + The last item in the list includes a behavior with trigger="visible". + The append action will fetch the next page of items and append them to + the list.

+
+
+
+
refresh
+
+

This trigger captures a “pull to refresh” action on + <list> and <view> items. + This interaction is associated with fetching up-to-date content from the backend. + Thus, it’s typically paired with an update or reload action to show the latest + data on the screen.

+
+
+
Pull-to-refresh trigger example
+
+
<body>
   <view scroll="true">
     <behavior trigger="refresh" action="reload" /> (1)
     <text>No items yet</text>
   </view>
 </body>
-
-
-
-
    -
  1. -

    When the view is pulled down to refresh, reload the screen

    -
  2. -
-
-
-

Note that adding a behavior with trigger="refresh" to a <view> or <list> will add the pull-to-refresh interaction to the element, including showing a spinner as the element is pulled down.

-
-
-
-
focus, blur, and change
-
-

These triggers are related to interactions with input elements. -Thus, they will only trigger behaviors attached to elements like <text-field>. -focus and blur will trigger when the user focuses and blurs the input element, respectively. -change will trigger when the value of the input element changes, like when the user types a letter in a text field. -These triggers are often used with behaviors that need to perform some server-side validation on the form fields. -For example, when the user types in a username and then blurs the field, a behavior could trigger on blur to make a request to the backend and check for uniqueness of the username. -If the entered username is not unique, the response could include an error message letting the user know they need to pick a different username.

-
-
-
-
-
Using Multiple Behaviors
-
-

Most of the examples shown above attach a single <behavior> to an element. -But there’s no such limitation in Hyperview; elements can define multiple behaviors. -We already saw an example where a single element had different actions triggered on press and longPress. -But we can also trigger multiple actions on the same trigger.

-
-
-

In this admittedly contrived example, we want to hide two elements on the screen when pressing the “Hide” button. -The two elements are far apart in the HXML, and cannot be hidden by hiding a common parent element. -But, we can trigger two behaviors at the same time, each one executing a “hide” action but targeting different elements.

-
-
-
Multiple behaviors triggering on press
-
-
<screen>
+                                
+
+
+
    +
  1. +

    When the view is pulled down to refresh, reload the screen

    +
  2. +
+
+
+

Note that adding a behavior with trigger="refresh" to a + <view> or <list> will add the pull-to-refresh + interaction to the element, including showing a spinner as the element is pulled + down.

+
+
+
+
focus, blur, and + change
+
+

These triggers are related to interactions with input elements. + Thus, they will only trigger behaviors attached to elements like + <text-field>. + focus and blur will trigger when the user focuses and + blurs the input element, respectively. + change will trigger when the value of the input element changes, like + when the user types a letter in a text field. + These triggers are often used with behaviors that need to perform some server-side + validation on the form fields. + For example, when the user types in a username and then blurs the field, a behavior + could trigger on blur to make a request to the backend and check for + uniqueness of the username. + If the entered username is not unique, the response could include an error message + letting the user know they need to pick a different username. +

+
+
+
+
+
Using Multiple Behaviors
+
+

Most of the examples shown above attach a single <behavior> to an + element. + But there’s no such limitation in Hyperview; elements can define multiple + behaviors. + We already saw an example where a single element had different actions triggered on + press and longPress. + But we can also trigger multiple actions on the same trigger.

+
+
+

In this admittedly contrived example, we want to hide two elements on the screen when + pressing the “Hide” button. + The two elements are far apart in the HXML, and cannot be hidden by hiding a common + parent element. + But, we can trigger two behaviors at the same time, each one executing a + “hide” action but targeting different elements.

+
+
+
Multiple behaviors triggering on press
+
+
<screen>
   <body>
     <text id="area1">Area 1</text>
 
@@ -15046,277 +21403,334 @@ But, we can trigger two behaviors at the same time, each one executing a “
     <text id="area2">Area 2</text>
   </body>
 </screen>
-
-
-
-
    -
  1. -

    Hide element with ID “area1” when pressed

    -
  2. -
  3. -

    Hide element with ID “area2” when pressed

    -
  4. -
-
-
-

Hyperview processes behaviors in the order they appear in the markup. -In this case, the element with ID “area1” will be hidden first, followed by the element with ID “area2”. -Since “hide” is an instantaneous action (ie, it doesn’t make an HTTP request), both elements will appear to hide simultaneously. -But what if we triggered two actions that depend on responses from HTTP requests (like “replace-inner”)? -In that case, each individual action is processed as soon as Hyperview receives the HTTP response. -Depending on network latency, the two actions could take effect in any order, and they are not guaranteed to be applied simultaneously.

-
-
-

We’ve seen elements with multiple behaviors and different triggers. -And we’ve seen elements with multiple behaviors with the same trigger. -These concepts can be mixed together too. -It’s not unusual for a production Hyperview app to contain several behaviors, some triggering together and others triggering on different interactions. -Using multiple behaviors with custom actions keeps HXML declarative, without sacrificing functionality.

-
-
-
-
-
-

12.4. Summary

-
-
    -
  • -

    Mobile app platforms push developers towards a thick-client architecture. But apps that use a thick client suffer from the same problems as SPAs on the web.

    -
  • -
  • -

    Using the hypermedia architecture for mobile apps solves the problems with thick-client apps.

    -
  • -
  • -

    HTML web views are one way to implement the hypermedia architecture for mobile apps. But HTML is not designed for mobile UIs, so this approach does not deliver a great user experience.

    -
  • -
  • -

    Hyperview is an alternative approach to build mobile apps using the hypermedia architecture. Hyperview introduces a new format called HXML. It also provides an open-source mobile thin-client to render HXML.

    -
  • -
  • -

    HXML looks similar to HTML, but it uses elements that correspond to mobile UIs, like <screen>, <header>, <list> and more.

    -
  • -
  • -

    HXML also includes input elements that implement common patterns in mobile apps, such as <switch>, <select-single>, and <select-multiple>.

    -
  • -
  • -

    New UI components can be added to HXML using namespaced elements. The Hyperview client can be easily extended to render these new elements.

    -
  • -
  • -

    Interactions in HXML are defined using <behavior> elements. Inspired by htmx, <behavior> elements decouple user interactions (triggers) from the resulting actions.

    -
  • -
  • -

    Navigation between screens in Hyperview is done using behaviors with navigation actions, like push, back, new, and close.

    -
  • -
  • -

    Updates to screens in Hyperview are defined using behaviors with update actions, such as replace and append.

    -
  • -
  • -

    System interactions in Hyperview are defined using behaviors with system actions, such as alert and share.

    -
  • -
  • -

    New actions can be added to HXML using namespaced attributes. The Hyperview client can be easily extended to interpret the new actions.

    -
  • -
  • -

    The extensibility of HXML and the Hyperview client make it easy for developers to define custom elements and behaviors. Developers can evolve Hyperview to suit their apps' requirements, while fully embracing the hypermedia architecture.

    -
  • -
-
-
-
-
-
-

13. Building a Contacts App With Hyperview

-
-
-

This chapter covers:

-
-
-
    -
  • -

    Transforming the existing Contacts web app into a native mobile app using Hyperview

    -
  • -
  • -

    Using Hyperview behaviors to navigate between screens using stacks and modals

    -
  • -
  • -

    Communicating between screens using the Events system

    -
  • -
  • -

    Using the same backend to power both the web app and mobile app

    -
  • -
-
-
-

Earlier chapters in this book explained the benefits of building apps using the hypermedia architecture. -These benefits were demonstrated by building a robust Contacts web application. -Then, Chapter 6 argued that hypermedia concepts can and should be applied to platforms other than the Web. -We introduced Hyperview as an example of a hypermedia format and client specifically designed for building mobile apps. -But you may still be wondering: what is it like to create a fully-featured, production-ready mobile app using Hyperview? -Do we have to learn a whole new language and framework? -In this chapter, we will show Hyperview in action by porting the Contacts web app to a native mobile app. -You will see that many web development techniques (and indeed, much of the code) are completely identical when developing with Hyperview. -How is that possible?

-
-
-
    -
  1. -

    Our Contacts web app was built with the principle of HATEOAS (Hypermedia as the Engine of Application State). -All of the app’s features (retrieving, searching, editing, and creating contacts) are implemented in the backend (the Contacts Python class). -Our mobile app, built with Hyperview, also leverages HATEOAS and relies on the backend for all of the app’s logic. -That means the Contacts Python class can power our mobile app the same way it powers the web app, without any changes required.

    -
  2. -
  3. -

    The client-server communication in the web app happens using HTTP. -The HTTP server for our web app is written using the Flask framework. -Hyperview also uses HTTP for client-server communication. -So we can re-use the Flask routes and views from the web app for the mobile app as well.

    -
  4. -
  5. -

    The web app uses HTML for its hypermedia format, and Hyperview uses HXML. -HTML and HXML are different formats, but the base syntax is similar (nested tags with attributes). -This means we can use the same templating library (Jinja) for HTML and HXML. -Additionally, many of the concepts of htmx are built into HXML. -We can directly port web app features implemented with htmx (search, infinite loading) to HXML.

    -
  6. -
-
-
-

Essentially, we can re-use almost everything from the web app backend, but we will need to replace the HTML templates with HXML templates. -Most of the sections in this chapter will assume we have the web contacts app running locally and listening on port 5000. -The focus will be on creating new HXML templates for our mobile app’s UI.

-
-
-

13.1. Creating a mobile app

-
-

But before we dive into HXML, there’s one pesky requirement: the Hyperview client. -When developing web applications, you only need to worry about the server because the client (web browser) is universally available. -There’s no equivalent Hyperview client installed on every mobile device. -Instead, we will create our own Hyperview client, customized to only talk to our server. -This client can be packaged up into an Android or iOS mobile app, and distributed through the respective app stores.

-
-
-

Luckily, we don’t need to start from scratch to implement a Hyperview client. -The Hyperview code repository comes with a demo backend and a demo client built using Expo. -We will use this demo client but point it to our contacts app backend as a starting point.

-
-
-
-
> git clone git@github.com:Instawork/hyperview.git
+                            
+
+
+
    +
  1. +

    Hide element with ID “area1” when pressed

    +
  2. +
  3. +

    Hide element with ID “area2” when pressed

    +
  4. +
+
+
+

Hyperview processes behaviors in the order they appear in the markup. + In this case, the element with ID “area1” will be hidden first, followed by + the element with ID “area2”. + Since “hide” is an instantaneous action (ie, it doesn’t make an HTTP + request), both elements will appear to hide simultaneously. + But what if we triggered two actions that depend on responses from HTTP requests (like + “replace-inner”)? + In that case, each individual action is processed as soon as Hyperview receives the HTTP + response. + Depending on network latency, the two actions could take effect in any order, and they + are not guaranteed to be applied simultaneously.

+
+
+

We’ve seen elements with multiple behaviors and different triggers. + And we’ve seen elements with multiple behaviors with the same trigger. + These concepts can be mixed together too. + It’s not unusual for a production Hyperview app to contain several behaviors, some + triggering together and others triggering on different interactions. + Using multiple behaviors with custom actions keeps HXML declarative, without sacrificing + functionality.

+
+
+
+
+
+

12.4. Summary

+
+
    +
  • +

    Mobile app platforms push developers towards a thick-client architecture. But apps that + use a thick client suffer from the same problems as SPAs on the web.

    +
  • +
  • +

    Using the hypermedia architecture for mobile apps solves the problems with thick-client + apps.

    +
  • +
  • +

    HTML web views are one way to implement the hypermedia architecture for mobile apps. But + HTML is not designed for mobile UIs, so this approach does not deliver a great user + experience.

    +
  • +
  • +

    Hyperview is an alternative approach to build mobile apps using the hypermedia + architecture. Hyperview introduces a new format called HXML. It also provides an + open-source mobile thin-client to render HXML.

    +
  • +
  • +

    HXML looks similar to HTML, but it uses elements that correspond to mobile UIs, like + <screen>, <header>, <list> and + more.

    +
  • +
  • +

    HXML also includes input elements that implement common patterns in mobile apps, such as + <switch>, <select-single>, and + <select-multiple>.

    +
  • +
  • +

    New UI components can be added to HXML using namespaced elements. The Hyperview client + can be easily extended to render these new elements.

    +
  • +
  • +

    Interactions in HXML are defined using <behavior> elements. Inspired + by htmx, <behavior> elements decouple user interactions (triggers) + from the resulting actions.

    +
  • +
  • +

    Navigation between screens in Hyperview is done using behaviors with navigation actions, + like push, back, new, and close.

    +
  • +
  • +

    Updates to screens in Hyperview are defined using behaviors with update actions, such as + replace and append.

    +
  • +
  • +

    System interactions in Hyperview are defined using behaviors with system actions, such as + alert and share.

    +
  • +
  • +

    New actions can be added to HXML using namespaced attributes. The Hyperview client can be + easily extended to interpret the new actions.

    +
  • +
  • +

    The extensibility of HXML and the Hyperview client make it easy for developers to define + custom elements and behaviors. Developers can evolve Hyperview to suit their apps' + requirements, while fully embracing the hypermedia architecture.

    +
  • +
+
+
+
+
+
+

13. Building a Contacts App With Hyperview

+
+
+

This chapter covers:

+
+
+
    +
  • +

    Transforming the existing Contacts web app into a native mobile app using Hyperview

    +
  • +
  • +

    Using Hyperview behaviors to navigate between screens using stacks and modals

    +
  • +
  • +

    Communicating between screens using the Events system

    +
  • +
  • +

    Using the same backend to power both the web app and mobile app

    +
  • +
+
+
+

Earlier chapters in this book explained the benefits of building apps using the hypermedia + architecture. + These benefits were demonstrated by building a robust Contacts web application. + Then, Chapter 6 argued that hypermedia concepts can and should be applied to platforms other than + the Web. + We introduced Hyperview as an example of a hypermedia format and client specifically designed for + building mobile apps. + But you may still be wondering: what is it like to create a fully-featured, production-ready mobile + app using Hyperview? + Do we have to learn a whole new language and framework? + In this chapter, we will show Hyperview in action by porting the Contacts web app to a native mobile + app. + You will see that many web development techniques (and indeed, much of the code) are completely + identical when developing with Hyperview. + How is that possible?

+
+
+
    +
  1. +

    Our Contacts web app was built with the principle of HATEOAS (Hypermedia as the Engine of + Application State). + All of the app’s features (retrieving, searching, editing, and creating contacts) are + implemented in the backend (the Contacts Python class). + Our mobile app, built with Hyperview, also leverages HATEOAS and relies on the backend for + all of the app’s logic. + That means the Contacts Python class can power our mobile app the same way it + powers the web app, without any changes required.

    +
  2. +
  3. +

    The client-server communication in the web app happens using HTTP. + The HTTP server for our web app is written using the Flask framework. + Hyperview also uses HTTP for client-server communication. + So we can re-use the Flask routes and views from the web app for the mobile app as well.

    +
  4. +
  5. +

    The web app uses HTML for its hypermedia format, and Hyperview uses HXML. + HTML and HXML are different formats, but the base syntax is similar (nested tags with + attributes). + This means we can use the same templating library (Jinja) for HTML and HXML. + Additionally, many of the concepts of htmx are built into HXML. + We can directly port web app features implemented with htmx (search, infinite loading) to + HXML.

    +
  6. +
+
+
+

Essentially, we can re-use almost everything from the web app backend, but we will need to replace + the HTML templates with HXML templates. + Most of the sections in this chapter will assume we have the web contacts app running locally and + listening on port 5000. + The focus will be on creating new HXML templates for our mobile app’s UI.

+
+
+

13.1. Creating a mobile app

+
+

But before we dive into HXML, there’s one pesky requirement: the Hyperview client. + When developing web applications, you only need to worry about the server because the client + (web browser) is universally available. + There’s no equivalent Hyperview client installed on every mobile device. + Instead, we will create our own Hyperview client, customized to only talk to our server. + This client can be packaged up into an Android or iOS mobile app, and distributed through the + respective app stores.

+
+
+

Luckily, we don’t need to start from scratch to implement a Hyperview client. + The Hyperview code repository comes with a demo backend and a demo client built using Expo. + We will use this demo client but point it to our contacts app backend as a starting point.

+
+
+
+
> git clone git@github.com:Instawork/hyperview.git
 > cd hyperview/demo
 > yarn (1)
 > yarn start (2)
-
-
-
-
    -
  1. -

    Install dependencies for the demo app

    -
  2. -
  3. -

    Start the Expo server to run the mobile app in the iOS simulator.

    -
  4. -
-
-
-

After running yarn start, you will be presented with a prompt asking you to open the mobile app using an Android emulator or iOS simulator. -Select an option based on which developer SDK you have installed. -(The screenshots in this chapter will be taken from the iOS simulator.) -With any luck, you will see the Expo mobile app installed in the simulator. -The mobile app will automatically launch and show a screen saying “Network request failed.” -That’s because by default, this app is configured to make a request to http://0.0.0.0:8085/index.xml, but our backend is listening on port 5000. -To fix this, we can make a simple configuration change in the demo/src/constants.js file:

-
-
-
-
//export const ENTRY_POINT_URL = 'http://0.0.0.0:8085/index.xml'; (1)
+                    
+
+
+
    +
  1. +

    Install dependencies for the demo app

    +
  2. +
  3. +

    Start the Expo server to run the mobile app in the iOS simulator.

    +
  4. +
+
+
+

After running yarn start, you will be presented with a prompt asking you to open the + mobile app using an Android emulator or iOS simulator. + Select an option based on which developer SDK you have installed. + (The screenshots in this chapter will be taken from the iOS simulator.) + With any luck, you will see the Expo mobile app installed in the simulator. + The mobile app will automatically launch and show a screen saying “Network request + failed.” + That’s because by default, this app is configured to make a request to + http://0.0.0.0:8085/index.xml, + but our backend is listening on port 5000. + To fix this, we can make a simple configuration change in the demo/src/constants.js + file:

+
+
+
+
//export const ENTRY_POINT_URL = 'http://0.0.0.0:8085/index.xml'; (1)
 export const ENTRY_POINT_URL = 'http://0.0.0.0:5000/'; (2)
-
-
-
-
    -
  1. -

    The default entrypoint URL in the demo app

    -
  2. -
  3. -

    Setting the URL to point to our contacts app

    -
  4. -
-
-
-

We’re not up and running yet. -With our Hyperview client now pointing to the right endpoint, we see a different error, a “ParseError”. -That’s because the backend is responding to requests with HTML content, but the Hyperview client expects an XML response (specifically, HXML). -So it’s time to turn our attention to our Flask backend. -We will go through the Flask views, and replace the HTML templates with HXML templates. -Specifically, let’s support the following features to our mobile app:

-
-
-
    -
  • -

    A searchable list of contacts

    -
  • -
  • -

    Viewing the details of a contact

    -
  • -
  • -

    Editing a contact

    -
  • -
  • -

    Deleting a contact

    -
  • -
  • -

    Adding a new contact

    -
  • -
-
- -
-
-

13.2. A Searchable List of Contacts

-
-

We will start building our Hyperview app with the entrypoint screen, the list of contacts. -For the initial version of this screen, let’s support the following features from the web app:

-
-
-
    -
  • -

    display a scrollable list of contacts

    -
  • -
  • -

    “search-as-you-type” field above the list

    -
  • -
  • -

    “infinite-scroll” to load more contacts as the user scrolls through

    -
  • -
-
-
-

Additionally, we will add a “pull-to-refresh” interaction on the list, since users expect this from list UIs in mobile apps.

-
-
-

If you recall, all of the pages in the Contacts web app extended a common base template, layout.html. -We need a similar base template for the screens of the mobile app. -This base template will contain the style rules or our UI, and a basic structure common to all screens. -Let’s call it layout.xml.

-
-
-
Base template hv/layout.xml
-
-
<doc xmlns="https://hyperview.org/hyperview">
+                    
+
+
+
    +
  1. +

    The default entrypoint URL in the demo app

    +
  2. +
  3. +

    Setting the URL to point to our contacts app

    +
  4. +
+
+
+

We’re not up and running yet. + With our Hyperview client now pointing to the right endpoint, we see a different error, a + “ParseError”. + That’s because the backend is responding to requests with HTML content, but the Hyperview + client expects an XML response (specifically, HXML). + So it’s time to turn our attention to our Flask backend. + We will go through the Flask views, and replace the HTML templates with HXML templates. + Specifically, let’s support the following features to our mobile app:

+
+
+
    +
  • +

    A searchable list of contacts

    +
  • +
  • +

    Viewing the details of a contact

    +
  • +
  • +

    Editing a contact

    +
  • +
  • +

    Deleting a contact

    +
  • +
  • +

    Adding a new contact

    +
  • +
+
+ +
+
+

13.2. A Searchable List of Contacts

+
+

We will start building our Hyperview app with the entrypoint screen, the list of contacts. + For the initial version of this screen, let’s support the following features from the web + app:

+
+
+
    +
  • +

    display a scrollable list of contacts

    +
  • +
  • +

    “search-as-you-type” field above the list

    +
  • +
  • +

    “infinite-scroll” to load more contacts as the user scrolls through

    +
  • +
+
+
+

Additionally, we will add a “pull-to-refresh” interaction on the list, since users + expect this from list UIs in mobile apps.

+
+
+

If you recall, all of the pages in the Contacts web app extended a common base template, + layout.html. + We need a similar base template for the screens of the mobile app. + This base template will contain the style rules or our UI, and a basic structure common to all + screens. + Let’s call it layout.xml.

+
+
+
Base template hv/layout.xml
+
+
<doc xmlns="https://hyperview.org/hyperview">
   <screen>
     <styles><!-- omitted for brevity --></styles>
     <body style="body" safe-area="true">
@@ -15332,29 +21746,33 @@ Let’s call it layout.xml.

</body> </screen> </doc>
-
-
-
-
    -
  1. -

    The header section of the template, with a default title

    -
  2. -
  3. -

    The content section of the template, to be provided by other templates.

    -
  4. -
-
-
-

We covered the HXML tags and attributes in the previous chapter. -This template sets up a basic screen layout using <doc>, <screen>, <body>, <header>, and <view> tags. -Note that the HXML syntax plays well with the Jinja templating library. -Here, we’re using Jinja’s blocks to define two sections (header and content) that will hold the unique content of a screen. -With our base template completed, we can create a template specifically for the contacts list screen.

-
-
-
Start of hv/index.xml
-
-
{% extends 'hv/layout.xml' %} (1)
+                    
+
+
+
    +
  1. +

    The header section of the template, with a default title

    +
  2. +
  3. +

    The content section of the template, to be provided by other templates.

    +
  4. +
+
+
+

We covered the HXML tags and attributes in the previous chapter. + This template sets up a basic screen layout using <doc>, + <screen>, <body>, <header>, and + <view> tags. + Note that the HXML syntax plays well with the Jinja templating library. + Here, we’re using Jinja’s blocks to define two sections (header and + content) that will hold the unique content of a screen. + With our base template completed, we can create a template specifically for the contacts list + screen.

+
+
+
Start of hv/index.xml
+
+
{% extends 'hv/layout.xml' %} (1)
 
 {% block content %} (2)
   <form> (3)
@@ -15364,37 +21782,44 @@ With our base template completed, we can create a template specifically for the
     </list>
   </form>
 {% endblock %}
-
-
-
-
    -
  1. -

    Extend the base layout template

    -
  2. -
  3. -

    Override the content block of the layout template

    -
  4. -
  5. -

    Create a search form that will issue an HTTP GET to /contacts

    -
  6. -
  7. -

    The list of contacts, using a Jinja include tag.

    -
  8. -
-
-
-

This template extends the base layout.xml, and overrides the content block with a <form>. -At first, it might seem strange that the form wraps both the <text-field> and the <list> elements. -But remember: in Hyperview, the form data gets included in any request originating from a child element. -We will soon add interactions to the list (pull to refresh) that will require the form data. -Note the use of a Jinja include tag to render the HXML for the rows of contacts in the list (hv/rows.xml). -Just like in the HTML templates, we can use the include to break up our HXML into smaller pieces. -It also allows the server to respond with just the rows.xml template for interactions like searching, infinite scroll, and pull-to-refresh.

-
-
-
hv/rows.xml
-
-
<items xmlns="https://hyperview.org/hyperview"> (1)
+                    
+
+
+
    +
  1. +

    Extend the base layout template

    +
  2. +
  3. +

    Override the content block of the layout template

    +
  4. +
  5. +

    Create a search form that will issue an HTTP GET to /contacts +

    +
  6. +
  7. +

    The list of contacts, using a Jinja include tag.

    +
  8. +
+
+
+

This template extends the base layout.xml, and overrides the content + block with a <form>. + At first, it might seem strange that the form wraps both the <text-field> and + the <list> elements. + But remember: in Hyperview, the form data gets included in any request originating from a child + element. + We will soon add interactions to the list (pull to refresh) that will require the form data. + Note the use of a Jinja include tag to render the HXML for the rows of contacts in + the list (hv/rows.xml). + Just like in the HTML templates, we can use the include to break up our HXML into + smaller pieces. + It also allows the server to respond with just the rows.xml template for + interactions like searching, infinite scroll, and pull-to-refresh.

+
+
+
hv/rows.xml
+
+
<items xmlns="https://hyperview.org/hyperview"> (1)
   {% for contact in contacts %} (2)
     <item key="{{ contact.id }}" style="contact-item"> (3)
       <text style="contact-item-label">
@@ -15409,37 +21834,43 @@ It also allows the server to respond with just the rows.xml templat
     </item>
   {% endfor %}
 </items>
-
-
-
-
    -
  1. -

    An HXML element that groups a set of <item> elements in a common parent

    -
  2. -
  3. -

    Iterate over the contacts that were passed in to the template

    -
  4. -
  5. -

    Render an <item> for each contact, showing the name, phone number, or email.

    -
  6. -
-
-
-

In the web app, each row in the list showed the contact’s name, phone number, and email address. -But in a mobile app, we have less real-estate. -It would be hard to cram all this information into one line. -Instead, the row just shows the contact’s first and last name, and falls back to email or phone if the name is not set. -To render the row, we again make use of Jinja template syntax to render dynamic text with data passed to the template.

-
-
-

We now have templates for the base layout, the contacts screen, and the contact rows. -But we still have to update the Flask views to use these templates. -Let’s take a look at the contacts() view in its current form, written for the web app:

-
-
-
app.py
-
-
@app.route("/contacts")
+                    
+
+
+
    +
  1. +

    An HXML element that groups a set of <item> elements in a common + parent

    +
  2. +
  3. +

    Iterate over the contacts that were passed in to the template

    +
  4. +
  5. +

    Render an <item> for each contact, showing the name, phone number, or + email.

    +
  6. +
+
+
+

In the web app, each row in the list showed the contact’s name, phone number, and email + address. + But in a mobile app, we have less real-estate. + It would be hard to cram all this information into one line. + Instead, the row just shows the contact’s first and last name, and falls back to email or + phone if the name is not set. + To render the row, we again make use of Jinja template syntax to render dynamic text with data + passed to the template.

+
+
+

We now have templates for the base layout, the contacts screen, and the contact rows. + But we still have to update the Flask views to use these templates. + Let’s take a look at the contacts() view in its current form, written for the + web app:

+
+
+
app.py
+
+
@app.route("/contacts")
 def contacts():
     search = request.args.get("q")
     page = int(request.args.get("page", 1))
@@ -15450,38 +21881,44 @@ def contacts():
     else:
         contacts_set = Contact.all(page)
     return render_template("index.html", contacts=contacts_set, page=page)
-
-
-
-

This view supports fetching a set of contacts based on two query params, q and page. -It also decides whether to render the full page (index.html) or just the contact rows (rows.html) based on the HX-Trigger header. -This presents a minor problem. -The HX-Trigger header is set by the htmx library; there’s no equivalent feature in Hyperview. -Moreover, there are multiple scenarios in Hyperview that require us to respond with just the contact rows:

-
-
-
    -
  • -

    searching

    -
  • -
  • -

    pull-to-refresh

    -
  • -
  • -

    loading the next page of contacts

    -
  • -
-
-
-

Since we can’t depend on a header like HX-Trigger, we need a different way to detect if the client needs the full screen or just the rows in the response. -We can do this by introducing a new query param, rows_only. -When this param has the value true, the view will respond to the request by rendering the rows.xml template. -Otherwise, it will respond with the index.xml template:

-
-
-
app.py
-
-
@app.route("/contacts")
+                    
+
+
+

This view supports fetching a set of contacts based on two query params, q and + page. + It also decides whether to render the full page (index.html) or just the contact + rows (rows.html) based on the HX-Trigger header. + This presents a minor problem. + The HX-Trigger header is set by the htmx library; there’s no equivalent + feature in Hyperview. + Moreover, there are multiple scenarios in Hyperview that require us to respond with just the + contact rows:

+
+
+
    +
  • +

    searching

    +
  • +
  • +

    pull-to-refresh

    +
  • +
  • +

    loading the next page of contacts

    +
  • +
+
+
+

Since we can’t depend on a header like HX-Trigger, we need a different way to + detect if the client needs the full screen or just the rows in the response. + We can do this by introducing a new query param, rows_only. + When this param has the value true, the view will respond to the request by + rendering the rows.xml template. + Otherwise, it will respond with the index.xml template:

+
+
+
app.py
+
+
@app.route("/contacts")
 def contacts():
     search = request.args.get("q")
     page = int(request.args.get("page", 1))
@@ -15493,95 +21930,107 @@ def contacts():
 
     template_name = "hv/rows.xml" if rows_only else "hv/index.xml" (2)
     return render_template(template_name, contacts=contacts_set, page=page)
-
-
-
-
    -
  1. -

    Check for a new rows_only query param

    -
  2. -
  3. -

    Render the appropriate HXML template based on rows_only

    -
  4. -
-
-
-

There’s one more change we have to make. -Flask assumes that most views will respond with HTML. -So Flask defaults the Content-Type response header to a value of text/html. -But the Hyperview client expects to receive HXML content, indicated by a Content-Type response header with value application/vnd.hyperview+xml. -The client will reject responses with a different content type. -To fix this, we need to explicitly set the Content-Type response header in our Flask views. -We will do this by introducing a new helper function, render_to_response():

-
-
-
app.py
-
-
def render_to_response(template_name, *args, **kwargs):
+                    
+
+
+
    +
  1. +

    Check for a new rows_only query param

    +
  2. +
  3. +

    Render the appropriate HXML template based on rows_only

    +
  4. +
+
+
+

There’s one more change we have to make. + Flask assumes that most views will respond with HTML. + So Flask defaults the Content-Type response header to a value of + text/html. + But the Hyperview client expects to receive HXML content, indicated by a + Content-Type response header with value application/vnd.hyperview+xml. + The client will reject responses with a different content type. + To fix this, we need to explicitly set the Content-Type response header in our + Flask views. + We will do this by introducing a new helper function, render_to_response():

+
+
+
app.py
+
+
def render_to_response(template_name, *args, **kwargs):
     content = render_template(template_name, *args, **kwargs) (1)
     response = make_response(content) (2)
     response.headers['Content-Type'] = 'application/vnd.hyperview+xml' (3)
     return response
-
-
-
-
    -
  1. -

    Renders the given template with the supplied arguments and keyword arguments.

    -
  2. -
  3. -

    Create an explicit response object with the rendered template

    -
  4. -
  5. -

    Sets the response Content-Type header to XML.

    -
  6. -
-
-
-

As you can see, this helper function uses render_template() under the hood. -render_template() returns a string. -This helper function uses that string to create an explicit Response object. -The response object has a headers attribute, allowing us to set and change the response headers. -Specifically, render_to_response() sets Content-Type to application/xml so that the Hyperview client recognizes the content. -This helper is a drop-in replacement for render_template in our views. -So all we need to do is update the last line of the contacts() function.

-
-
-
contacts() function
-
-
return render_to_response(template_name, contacts=contacts_set, page=page) (1)
-
-
-
-
    -
  1. -

    Render the HXML template to an XML response.

    -
  2. -
-
-
-

With these changes to the contacts() view, we can finally see the fruits of our labor. -After restarting the backend and refreshing the screen in our mobile app, we can see the contacts screen!

-
-
-
-screenshot hyperview list -
-
Figure 11. Contacts Screen
-
-
-

Searching Contacts

-
-

So far, we have a mobile app that displays a screen with a list of contacts. -But our UI doesn’t support any interactions. -Typing a query in the search field doesn’t filter the list of contacts. -Let’s add a behavior to the search field to implement a search-as-you-type interaction. -This requires expanding <text-field> to add a <behavior> element.

-
-
-
Snippet of hv/index.xml
-
-
<text-field name="q" value="" placeholder="Search..." style="search-field">
+                    
+
+
+
    +
  1. +

    Renders the given template with the supplied arguments and keyword arguments.

    +
  2. +
  3. +

    Create an explicit response object with the rendered template

    +
  4. +
  5. +

    Sets the response Content-Type header to XML.

    +
  6. +
+
+
+

As you can see, this helper function uses render_template() under the hood. + render_template() returns a string. + This helper function uses that string to create an explicit Response object. + The response object has a headers attribute, allowing us to set and change the + response headers. + Specifically, render_to_response() sets Content-Type to + application/xml so that the Hyperview client recognizes the content. + This helper is a drop-in replacement for render_template in our views. + So all we need to do is update the last line of the contacts() function. +

+
+
+
contacts() function
+
+
return render_to_response(template_name, contacts=contacts_set, page=page) (1)
+
+
+
+
    +
  1. +

    Render the HXML template to an XML response.

    +
  2. +
+
+
+

With these changes to the contacts() view, we can finally see the fruits of our + labor. + After restarting the backend and refreshing the screen in our mobile app, we can see the + contacts screen!

+
+
+
+ screenshot hyperview list +
+
Figure 11. Contacts Screen
+
+
+

Searching Contacts

+
+

So far, we have a mobile app that displays a screen with a list of contacts. + But our UI doesn’t support any interactions. + Typing a query in the search field doesn’t filter the list of contacts. + Let’s add a behavior to the search field to implement a search-as-you-type + interaction. + This requires expanding <text-field> to add a + <behavior> element.

+
+
+
Snippet of hv/index.xml
+
+
<text-field name="q" value="" placeholder="Search..." style="search-field">
   <behavior
     trigger="change" (1)
     action="replace-inner" (2)
@@ -15590,98 +22039,130 @@ This requires expanding <text-field> to add a <behav
     verb="get" (5)
   />
 </text-field>
-
-
-
-
    -
  1. -

    This behavior will trigger when the value of the text field changes

    -
  2. -
  3. -

    When the behavior triggers, the action will replace the content inside the target element.

    -
  4. -
  5. -

    The target of the action is the element with ID contacts-list.

    -
  6. -
  7. -

    The replacement content will be fetched from this URL path.

    -
  8. -
  9. -

    The replacement content will be fetched with the GET HTTP method.

    -
  10. -
-
-
-

The first thing you’ll notice is that we changed the text field from using a self-closing tag (<text-field />) to using opening and closing tags (<text-field>…​</text-field>). -This allows us to add a child <behavior> element to define an interaction. -The trigger="change" attribute tells Hyperview that a change to the value of the text field will trigger an action. -Any time the user edits the content of the text field by adding or deleting characters, an action will trigger. -The remaining attributes on the <behavior> element define the action. -action="replace-inner" means the action will update content on the screen, by replacing the HXML content of an element with new content. -For replace-inner to do its thing, we need to know two things: the current element on the screen that will be targeted by the action, and the content that will used for the replacement. -target="contacts-list" tells us the ID of the current element. -Note that we set id="contacts-list" on the <list> element in index.xml. -So when the user enters a search query into the text field, Hyperview will replace the content of <list> (a bunch of <item> elements) -with new content (<item> elements that match the search query) received in the relative href response -(the domain is inferred from the domain used to fetch the screen). -Note that href includes our rows_only query param; we want the response to only include the rows and not the entire screen.

-
-
-
-screenshot hyperview search -
-
Figure 12. Searching for Contacts
-
-
-

That’s all it takes to add search-as-you-type functionality to our mobile app! -As the user types a search query, the client will make requests to the backend and replace the list with the search results. -You may be wondering, how does the backend know the query to use? -The href attribute in the behavior does not include the q param expected by our backend. -But remember, in index.xml, we wrapped the <text-field> and <list> elements with a parent <form> element. -The <form> element defines a group of inputs that will be serialized and included in any HTTP requests triggered by its child elements. -In this case, the <form> element surrounds the search behavior and the text field. -So the value of the <text-field> will be included in our HTTP request for the search results. -Since we are making a GET request, the name and value of the text field will be serialized as a query param. -Any existing query params on the href will be preserved. -This means the actual HTTP request to our backend looks like GET /contacts?rows_only=true&q=Car. -Our backend already supports the q param for searching, so the response will include rows that match the string “Car”.

-
-
-
-

Infinite scroll

-
-

If the user has hundreds or thousands of contacts, loading them all at once may result in poor app performance. -That’s why most mobile apps with long lists implement an interaction known as “infinite scroll”. -The app loads a fixed number of initial items in the list, let’s say 100 items. -If the user scrolls to the bottom of the list, they see a spinner indicating more content is loading. -Once the content is available, the spinner is replaced with the next page of 100 items. -These items are appended to the list, they don’t replace the first set of items. -So the list now contains 200 items. -If the user scrolls to the bottom of the list again, they will see another spinner, and the app will load the next set of content. -Infinite scroll improves app performance in two ways:

-
-
-
    -
  • -

    The initial request for 100 items will be processed quickly, with predictable latency.

    -
  • -
  • -

    Subsequent requests can also be fast and predictable.

    -
  • -
  • -

    If the user doesn’t scroll to the bottom of the list, the app won’t have to make subsequent requests.

    -
  • -
-
-
-

Our Flask backend already supports pagination on the /contacts endpoint via the page query param. -We just need to modify our HXML templates to make use of this parameter. -To do this, let’s edit rows.xml to add a new <item> below the Jinja for-loop:

-
-
-
Snippet of hv/rows.xml
-
-
<items xmlns="https://hyperview.org/hyperview">
+                        
+
+
+
    +
  1. +

    This behavior will trigger when the value of the text field changes

    +
  2. +
  3. +

    When the behavior triggers, the action will replace the content inside the target + element.

    +
  4. +
  5. +

    The target of the action is the element with ID contacts-list.

    +
  6. +
  7. +

    The replacement content will be fetched from this URL path.

    +
  8. +
  9. +

    The replacement content will be fetched with the GET HTTP method.

    +
  10. +
+
+
+

The first thing you’ll notice is that we changed the text field from using a + self-closing tag (<text-field />) to using opening and closing tags + (<text-field>…​</text-field>). + This allows us to add a child <behavior> element to define an + interaction. + The trigger="change" attribute tells Hyperview that a change to the value of + the text field will trigger an action. + Any time the user edits the content of the text field by adding or deleting characters, an + action will trigger. + The remaining attributes on the <behavior> element define the action. + action="replace-inner" means the action will update content on the screen, by + replacing the HXML content of an element with new content. + For replace-inner to do its thing, we need to know two things: the current + element on the screen that will be targeted by the action, and the content that will used + for the replacement. + target="contacts-list" tells us the ID of the current element. + Note that we set id="contacts-list" on the <list> element in + index.xml. + So when the user enters a search query into the text field, Hyperview will replace the + content of <list> (a bunch of <item> elements) + with new content (<item> elements that match the search query) received + in the relative href response + (the domain is inferred from the domain used to fetch the screen). + Note that href includes our rows_only query param; we want the + response to only include the rows and not the entire screen. +

+
+
+
+ screenshot hyperview search +
+
Figure 12. Searching for Contacts
+
+
+

That’s all it takes to add search-as-you-type functionality to our mobile app! + As the user types a search query, the client will make requests to the backend and replace + the list with the search results. + You may be wondering, how does the backend know the query to use? + The href attribute in the behavior does not include the q param + expected by our backend. + But remember, in index.xml, we wrapped the <text-field> and + <list> elements with a parent <form> element. + The <form> element defines a group of inputs that will be serialized and + included in any HTTP requests triggered by its child elements. + In this case, the <form> element surrounds the search behavior and the + text field. + So the value of the <text-field> will be included in our HTTP request for + the search results. + Since we are making a GET request, the name and value of the text field will be + serialized as a query param. + Any existing query params on the href will be preserved. + This means the actual HTTP request to our backend looks like + GET /contacts?rows_only=true&q=Car. + Our backend already supports the q param for searching, so the response will + include rows that match the string “Car”.

+
+
+
+

Infinite scroll

+
+

If the user has hundreds or thousands of contacts, loading them all at once may result in + poor app performance. + That’s why most mobile apps with long lists implement an interaction known as + “infinite scroll”. + The app loads a fixed number of initial items in the list, let’s say 100 items. + If the user scrolls to the bottom of the list, they see a spinner indicating more content is + loading. + Once the content is available, the spinner is replaced with the next page of 100 items. + These items are appended to the list, they don’t replace the first set of items. + So the list now contains 200 items. + If the user scrolls to the bottom of the list again, they will see another spinner, and the + app will load the next set of content. + Infinite scroll improves app performance in two ways:

+
+
+
    +
  • +

    The initial request for 100 items will be processed quickly, with predictable + latency.

    +
  • +
  • +

    Subsequent requests can also be fast and predictable.

    +
  • +
  • +

    If the user doesn’t scroll to the bottom of the list, the app won’t have + to make subsequent requests.

    +
  • +
+
+
+

Our Flask backend already supports pagination on the /contacts endpoint via the + page query param. + We just need to modify our HXML templates to make use of this parameter. + To do this, let’s edit rows.xml to add a new <item> + below the Jinja for-loop:

+
+
+
Snippet of hv/rows.xml
+
+
<items xmlns="https://hyperview.org/hyperview">
   {% for contact in contacts %}
     <item key="{{ contact.id }}" style="contact-item">
       <!-- omitted for brevity -->
@@ -15700,80 +22181,101 @@ To do this, let’s edit rows.xml to add a new <item&g
     </item>
   {% endif %}
 </items>
-
-
-
-
    -
  1. -

    Include an extra <item> in the list to show the spinner

    -
  2. -
  3. -

    The item behavior triggers when visible in the viewport

    -
  4. -
  5. -

    When triggered, the behavior will replace an element on the screen

    -
  6. -
  7. -

    The element to be replaced is the item itself (ID load-more)

    -
  8. -
  9. -

    Replace the item with the next page of content

    -
  10. -
  11. -

    The spinner element

    -
  12. -
-
-
-

If the current list of contacts passed to the template is empty, we can assume there’s no more contacts to fetch from the backend. -So we use a Jinja conditional to only include this new <item> if the list of contacts is non-empty. -This new <item> element gets an ID and a behavior. -The behavior defines the infinite scroll interaction. -Up until now, we’ve seen trigger values of change and refresh. -But to implement infinite scroll, we need a way to trigger the action when the user scrolls to the bottom of the list. -The visible trigger can be used for this exact purpose. -It will trigger the action when the element with the behavior is visible in the device viewport. -In this case, the new <item> element is the last item in the list, so the action will trigger when the user scrolls down far enough for the item to enter the viewport. -As soon as the item is visible, the action will make an HTTP GET request, and replace the loading <item> element with the response content. -Note that our href must include the rows_only=true query param, so that our response will only include HXML for the contact items, and not the entire screen. -Also, we’re passing the page query param, incrementing the current page number to ensure we load the next page.

-
-
-

What happens when there’s more than one page of items? -The initial screen will include the first 100 items, plus the “load-more” item at the bottom. -When the user scrolls to the bottom of the screen, Hyperview will request the second page of items (&page=2), and replace the “load-more” item with the new items. -But this second page of items will include a new “load-more” item. -So once the user scrolls through all of the items from the second page, Hyperview will again request more items (&page=3). -And once again, the “load-more” item will be replaced with the new items. -This will continue until all of the items will be loaded on the screen. -At that point, there will be no more contacts to return, the response will not include another “load-more” item, and our pagination is over.

-
-
-
-

Pull-to-refresh

-
-

Pull-to-refresh is a common interaction in mobile apps, especially on screens featuring dynamic content. -It works like this: -At the top of a scrolling view, the user pulls the scrolling content downwards with a swipe-down gesture. -This reveals a spinner “below” the content. -Pulling the content down sufficiently far will trigger a refresh. -While the content refreshes, the spinner remains visible on screen, indicating to the user that the action is still taking place. -Once the content is refreshed, the content retracts back up to its default position, hiding the spinner and letting the user know that the interaction is done.

-
-
-
-screenshot hyperview refresh cropped -
-
Figure 13. Pull-to-refresh
-
-
-

This pattern is so common and useful that it’s built in to Hyperview via the refresh action. -Let’s add pull-to-refresh to our list of contacts to see it in action.

-
-
-
Snippet of hv/index.xml
-
-
<list id="contacts-list"
+                        
+
+
+
    +
  1. +

    Include an extra <item> in the list to show the spinner

    +
  2. +
  3. +

    The item behavior triggers when visible in the viewport

    +
  4. +
  5. +

    When triggered, the behavior will replace an element on the screen

    +
  6. +
  7. +

    The element to be replaced is the item itself (ID load-more)

    +
  8. +
  9. +

    Replace the item with the next page of content

    +
  10. +
  11. +

    The spinner element

    +
  12. +
+
+
+

If the current list of contacts passed to the template is empty, we can assume there’s + no more contacts to fetch from the backend. + So we use a Jinja conditional to only include this new <item> if the list + of contacts is non-empty. + This new <item> element gets an ID and a behavior. + The behavior defines the infinite scroll interaction. + Up until now, we’ve seen trigger values of change and + refresh. + But to implement infinite scroll, we need a way to trigger the action when the user scrolls + to the bottom of the list. + The visible trigger can be used for this exact purpose. + It will trigger the action when the element with the behavior is visible in the device + viewport. + In this case, the new <item> element is the last item in the list, so the + action will trigger when the user scrolls down far enough for the item to enter the + viewport. + As soon as the item is visible, the action will make an HTTP GET request, and replace the + loading <item> element with the response content. + Note that our href must include the rows_only=true query param, so that our + response will only include HXML for the contact items, and not the entire screen. + Also, we’re passing the page query param, incrementing the current page + number to ensure we load the next page.

+
+
+

What happens when there’s more than one page of items? + The initial screen will include the first 100 items, plus the “load-more” item + at the bottom. + When the user scrolls to the bottom of the screen, Hyperview will request the second page of + items (&page=2), and replace the “load-more” item with the new + items. + But this second page of items will include a new “load-more” item. + So once the user scrolls through all of the items from the second page, Hyperview will again + request more items (&page=3). + And once again, the “load-more” item will be replaced with the new items. + This will continue until all of the items will be loaded on the screen. + At that point, there will be no more contacts to return, the response will not include + another “load-more” item, and our pagination is over.

+
+
+
+

Pull-to-refresh

+
+

Pull-to-refresh is a common interaction in mobile apps, especially on screens featuring + dynamic content. + It works like this: + At the top of a scrolling view, the user pulls the scrolling content downwards with a + swipe-down gesture. + This reveals a spinner “below” the content. + Pulling the content down sufficiently far will trigger a refresh. + While the content refreshes, the spinner remains visible on screen, indicating to the user + that the action is still taking place. + Once the content is refreshed, the content retracts back up to its default position, hiding + the spinner and letting the user know that the interaction is done.

+
+
+
+ screenshot hyperview refresh cropped +
+
Figure 13. Pull-to-refresh
+
+
+

This pattern is so common and useful that it’s built in to Hyperview via the + refresh action. + Let’s add pull-to-refresh to our list of contacts to see it in action.

+
+
+
Snippet of hv/index.xml
+
+
<list id="contacts-list"
   trigger="refresh" (1)
   action="replace-inner" (2)
   target="contacts-list" (3)
@@ -15782,55 +22284,68 @@ Let’s add pull-to-refresh to our list of contacts to see it in action.

> {% include 'hv/rows.xml' %} </list>
-
-
-
-
    -
  1. -

    This behavior will trigger when the user does a “pull-to-refresh” gesture.

    -
  2. -
  3. -

    When the behavior triggers, this action will replace the content inside the target element.

    -
  4. -
  5. -

    The target of the action is the <list> element itself.

    -
  6. -
  7. -

    The replacement content will be fetched from this URL path.

    -
  8. -
  9. -

    The replacement content will be fetched with the GET HTTP method.

    -
  10. -
-
-
-

You’ll notice something unusual in the snippet above: rather than adding a <behavior> element to the <list>, we added the behavior attributes directly to the <list> element. -This is a shorthand notation that’s sometimes useful for specifying single behaviors on an element. -It is equivalent to adding a <behavior> element to the <list> with the same attributes. -So why did we use the shorthand syntax here? -It has to do with the action, replace-inner. -Remember, this action replaces all child elements of the target with the new content. -This includes <behavior> elements too! -Let’s say our <list> did contain a <behavior>. -If the user did a search or pull-to-refresh, we would replace the content of <list> with the content from rows.xml. -The <behavior> would no longer be defined on the <list>, and subsequent attempts to pull-to-refresh would not work. -By defining the behavior as attributes of <list>, the behavior will persist even when replacing the items in the list. -Generally, we prefer to use explicit <behavior> elements in my HXML. -It makes it easier to define multiple behaviors, and to move the behavior around while refactoring. -But the shorthand syntax is good to apply in situations like this.

-
-
-
-

Viewing The Details Of A Contact

-
-

Now that our contacts list screen is in good shape, we can start adding other screens to our app. -The natural next step is to create a details screen, which appears when the user taps an item in the contacts list. -Let’s update the template that renders the contact <item> elements, and add a behavior to show the details screen.

-
-
-
hv/rows.xml
-
-
<items xmlns="https://hyperview.org/hyperview">
+                        
+
+
+
    +
  1. +

    This behavior will trigger when the user does a “pull-to-refresh” + gesture.

    +
  2. +
  3. +

    When the behavior triggers, this action will replace the content inside the target + element.

    +
  4. +
  5. +

    The target of the action is the <list> element itself.

    +
  6. +
  7. +

    The replacement content will be fetched from this URL path.

    +
  8. +
  9. +

    The replacement content will be fetched with the GET HTTP method.

    +
  10. +
+
+
+

You’ll notice something unusual in the snippet above: rather than adding a + <behavior> element to the <list>, we added the + behavior attributes directly to the <list> element. + This is a shorthand notation that’s sometimes useful for specifying single behaviors + on an element. + It is equivalent to adding a <behavior> element to the + <list> with the same attributes. + So why did we use the shorthand syntax here? + It has to do with the action, replace-inner. + Remember, this action replaces all child elements of the target with the new content. + This includes <behavior> elements too! + Let’s say our <list> did contain a <behavior>. + If the user did a search or pull-to-refresh, we would replace the content of + <list> with the content from rows.xml. + The <behavior> would no longer be defined on the + <list>, and subsequent attempts to pull-to-refresh would not work. + By defining the behavior as attributes of <list>, the behavior will + persist even when replacing the items in the list. + Generally, we prefer to use explicit <behavior> elements in my HXML. + It makes it easier to define multiple behaviors, and to move the behavior around while + refactoring. + But the shorthand syntax is good to apply in situations like this.

+
+
+
+

Viewing The Details Of A Contact

+
+

Now that our contacts list screen is in good shape, we can start adding other screens to our + app. + The natural next step is to create a details screen, which appears when the user taps an + item in the contacts list. + Let’s update the template that renders the contact <item> elements, + and add a behavior to show the details screen.

+
+
+
hv/rows.xml
+
+
<items xmlns="https://hyperview.org/hyperview">
   {% for contact in contacts %}
     <item key="{{ contact.id }}" style="contact-item">
       <behavior trigger="press" action="push" href="/contacts/{{ contact.id }}" /> (1)
@@ -15840,48 +22355,56 @@ Let’s update the template that renders the contact <item>
-
-
-
-
    -
  1. -

    Behavior to push the contact details screen onto the stack when pressed.

    -
  2. -
-
-
-

Our Flask backend already has a route for serving the contact details at /contacts/<contact_id>. -In our template, we use a Jinja variable to dynamically generate the URL path for the current contact in the for-loop. -We also used the “push” action to show the details by pushing a new screen onto the stack. -If you reload the app, you can now tap any contact in the list, and Hyperview will open the new screen. -However, the new screen will show an error message. -That’s because our backend is still returning HTML in the response, and the Hyperview client expects HXML. -Let’s update the backend to respond with HXML and the proper headers.

-
-
-
app.py
-
-
@app.route("/contacts/<contact_id>")
+                        
+
+
+
    +
  1. +

    Behavior to push the contact details screen onto the stack when pressed.

    +
  2. +
+
+
+

Our Flask backend already has a route for serving the contact details at + /contacts/<contact_id>. + In our template, we use a Jinja variable to dynamically generate the URL path for the + current contact in the for-loop. + We also used the “push” action to show the details by pushing a new screen onto + the stack. + If you reload the app, you can now tap any contact in the list, and Hyperview will open the + new screen. + However, the new screen will show an error message. + That’s because our backend is still returning HTML in the response, and the Hyperview + client expects HXML. + Let’s update the backend to respond with HXML and the proper headers.

+
+
+
app.py
+
+
@app.route("/contacts/<contact_id>")
 def contacts_view(contact_id=0):
     contact = Contact.find(contact_id)
     return render_to_response("hv/show.xml", contact=contact) (1)
-
-
-
-
    -
  1. -

    Generate an XML response from a new template file.

    -
  2. -
-
-
-

Just like with the contacts() view, contacts_view() uses render_to_response() to set the Content-Type header on the response. -We’re also generating the response from a new HXML template, which we can create now:

-
-
-
hv/show.xml
-
-
{% extends 'hv/layout.xml' %} (1)
+                        
+
+
+
    +
  1. +

    Generate an XML response from a new template file.

    +
  2. +
+
+
+

Just like with the contacts() view, contacts_view() uses + render_to_response() to set the Content-Type header on the + response. + We’re also generating the response from a new HXML template, which we can create now: +

+
+
+
hv/show.xml
+
+
{% extends 'hv/layout.xml' %} (1)
 
 {% block header %} (2)
   <text style="header-button">
@@ -15905,72 +22428,86 @@ We’re also generating the response from a new HXML template, which we can
   </view>
 </view>
 {% endblock %}
-
-
-
-
    -
  1. -

    Extend the base layout template

    -
  2. -
  3. -

    Override the header block of the layout template to include a "Back" button

    -
  4. -
  5. -

    Behavior to navigate to the previous screen when pressed

    -
  6. -
  7. -

    Override the content block to show the full details of the selected contact.

    -
  8. -
-
-
-

The contacts detail screen extends the base layout.xml template, just like we did in index.xml. -This time, we’re overriding content in both the header block and content block. -Overriding the header block lets us add a "Back" button with a behavior. -When pressed, the Hyperview client will unwind the navigation stack and return the user to the contacts list. -Note that triggering this behavior is not the only way to navigate back. -The Hyperview client respects navigation conventions on different platforms. -On iOS, users can also navigate to the previous screen by swiping right from the left edge of the device. -On Android, users can also navigate to the previous screen by pressing the hardware back button. -We don’t need to specify anything extra in the HXML to get these interactions!

-
-
-
-screenshot hyperview detail cropped -
-
Figure 14. Contact Details Screen
-
-
-

With just a few simple changes, we’ve gone from a single-screen app to a multi-screen app. -Note that we didn’t need to change anything in the actual mobile app code to support our new screen. -This is a big deal. -In traditional mobile app development, adding screens can be a significant task. -Developers need to create the new screen, insert it into the appropriate place of the navigation hierarchy, and write code to open the new screen from existing screens. -In Hyperview, we just added a behavior with action="push".

-
-
-
-
-

13.3. Editing a Contact

-
-

So far, our app lets us browse a list of contacts, and view details of a specific contact. -Wouldn’t it be nice to update the name, phone number, or email of a contact? -Let’s add UI to edit contacts as our next enhancement.

-
-
-

First we have to figure out how we want to display the editing UI. -We could push a new editing screen onto the stack, the same way we pushed the contact details screen. -But that’s not the best design from a user-experience perspective. -Pushing new screens makes sense when drilling down into data, like going from a list to a single item. -But editing is not a “drill-down” interaction, it’s a mode switch between viewing and editing. -So instead of pushing a new screen, let’s replace the current screen with the editing UI. -That means we need to add a button and behavior that use the reload action. -This button can be added to the header of the contact details screen.

-
-
-
Snippet of hv/show.xml
-
-
{% block header %}
+                        
+
+
+
    +
  1. +

    Extend the base layout template

    +
  2. +
  3. +

    Override the header block of the layout template to include a "Back" + button

    +
  4. +
  5. +

    Behavior to navigate to the previous screen when pressed

    +
  6. +
  7. +

    Override the content block to show the full details of the selected + contact.

    +
  8. +
+
+
+

The contacts detail screen extends the base layout.xml template, just like we + did in index.xml. + This time, we’re overriding content in both the header block and + content block. + Overriding the header block lets us add a "Back" button with a behavior. + When pressed, the Hyperview client will unwind the navigation stack and return the user to + the contacts list. + Note that triggering this behavior is not the only way to navigate back. + The Hyperview client respects navigation conventions on different platforms. + On iOS, users can also navigate to the previous screen by swiping right from the left edge + of the device. + On Android, users can also navigate to the previous screen by pressing the hardware back + button. + We don’t need to specify anything extra in the HXML to get these interactions!

+
+
+
+ screenshot hyperview detail cropped +
+
Figure 14. Contact Details Screen
+
+
+

With just a few simple changes, we’ve gone from a single-screen app to a multi-screen + app. + Note that we didn’t need to change anything in the actual mobile app code to support + our new screen. + This is a big deal. + In traditional mobile app development, adding screens can be a significant task. + Developers need to create the new screen, insert it into the appropriate place of the + navigation hierarchy, and write code to open the new screen from existing screens. + In Hyperview, we just added a behavior with action="push".

+
+
+
+
+

13.3. Editing a Contact

+
+

So far, our app lets us browse a list of contacts, and view details of a specific contact. + Wouldn’t it be nice to update the name, phone number, or email of a contact? + Let’s add UI to edit contacts as our next enhancement.

+
+
+

First we have to figure out how we want to display the editing UI. + We could push a new editing screen onto the stack, the same way we pushed the contact details + screen. + But that’s not the best design from a user-experience perspective. + Pushing new screens makes sense when drilling down into data, like going from a list to a single + item. + But editing is not a “drill-down” interaction, it’s a mode switch between + viewing and editing. + So instead of pushing a new screen, let’s replace the current screen with the editing UI. + That means we need to add a button and behavior that use the reload action. + This button can be added to the header of the contact details screen.

+
+
+
Snippet of hv/show.xml
+
+
{% block header %}
   <text style="header-button">
     <behavior trigger="press" action="back" />
     Back
@@ -15981,28 +22518,32 @@ This button can be added to the header of the contact details screen.

Edit </text> {% endblock %}
-
-
-
-
    -
  1. -

    The new “Edit” button

    -
  2. -
  3. -

    Behavior to reload the current screen with the edit screen when pressed

    -
  4. -
-
-
-

Once again, we’re reusing an existing Flask route (/contacts/<contact_id>/edit) for the edit UI, and filling in the contact ID using data passed to the Jinja template. -We also need to update the contacts_edit_get() view to return an XML response based on an HXML template (hv/edit.xml). -I’ll skip the code sample because the needed changes are identical to what we applied to contacts_view() in the previous section. -Instead, let’s focus on the template for the edit screen.

-
-
-
hv/edit.xml
-
-
{% extends 'hv/layout.xml' %}
+                    
+
+
+
    +
  1. +

    The new “Edit” button

    +
  2. +
  3. +

    Behavior to reload the current screen with the edit screen when pressed

    +
  4. +
+
+
+

Once again, we’re reusing an existing Flask route + (/contacts/<contact_id>/edit) for the edit UI, and filling in the contact ID + using data passed to the Jinja template. + We also need to update the contacts_edit_get() view to return an XML response based + on an HXML template (hv/edit.xml). + I’ll skip the code sample because the needed changes are identical to what we applied to + contacts_view() in the previous section. + Instead, let’s focus on the template for the edit screen.

+
+
+
hv/edit.xml
+
+
{% extends 'hv/layout.xml' %}
 
 {% block header %}
   <text style="header-button">
@@ -16029,34 +22570,37 @@ Instead, let’s focus on the template for the edit screen.

</view> </form> {% endblock %}
-
-
-
-
    -
  1. -

    Form wrapping the input fields and buttons

    -
  2. -
  3. -

    Container with ID, containing the input fields

    -
  4. -
  5. -

    Template include to render the input fields

    -
  6. -
  7. -

    Button to submit the form data and update the input fields container

    -
  8. -
-
-
-

Since the edit screen needs to send data to the backend, we wrap the entire content section in a <form> element. -This ensures the form field data will be included in the HTTP requests to our backend. -Within the <form> element, our UI is divided into two sections: the form fields, and the Save button. -The actual form fields are defined in a separate template (form_fields.xml) and added to the edit screen using a Jinja include tag.

-
-
-
hv/form_fields.xml
-
-
<view style="edit-group">
+                    
+
+
+
    +
  1. +

    Form wrapping the input fields and buttons

    +
  2. +
  3. +

    Container with ID, containing the input fields

    +
  4. +
  5. +

    Template include to render the input fields

    +
  6. +
  7. +

    Button to submit the form data and update the input fields container

    +
  8. +
+
+
+

Since the edit screen needs to send data to the backend, we wrap the entire content section in a + <form> element. + This ensures the form field data will be included in the HTTP requests to our backend. + Within the <form> element, our UI is divided into two sections: the form + fields, and the Save button. + The actual form fields are defined in a separate template (form_fields.xml) and + added to the edit screen using a Jinja include tag.

+
+
+
hv/form_fields.xml
+
+
<view style="edit-group">
   <view style="edit-field">
     <text-field name="first_name" placeholder="First name" value="{{ contact.first }}" /> (1)
     <text style="edit-field-error">{{ contact.errors.first }}</text> (2)
@@ -16069,66 +22613,79 @@ The actual form fields are defined in a separate template (form_fields.xml
 
   <!-- same markup for contact.email and contact.phone -->
 </view>
-
-
-
-
    -
  1. -

    Text input holding the current value for the contact’s first name

    -
  2. -
  3. -

    Text element that could display errors from the contact model

    -
  4. -
  5. -

    Another text field, this time for the contact’s last name

    -
  6. -
-
-
-

I omitted the code for the contact’s phone number and email address, because they follow the same pattern as the first and last name. -Each contact field has its own <text-field>, and a <text> element below it to display possible errors. -The <text-field> has two important attributes:

-
-
-
    -
  • -

    name defines the name to use when serializing the text-field’s value into form data for HTTP requests. -We are using the same names as the web app from previous chapters (first_name, last_name, phone, email). -That way, we don’t need to make changes in our backend to parse the form data.

    -
  • -
  • -

    value defines the pre-filled data in the text field. -Since we are editing an existing contact, it makes sense to pre-fill the text field with the current name, phone, or email.

    -
  • -
-
-
-

You might be wondering, why did we choose to define the form fields in a separate template (form_fields.xml)? -To understand that decision, we need to first discuss the “Save” button. -When pressed, the Hyperview client will make an HTTP POST request to contacts/<contact_id>/edit, with form data serialized from the <text-field> inputs. -The HXML response will replace the contents of form field container (ID form-fields). -But what should that response be? -That depends on the validity of the form data:

-
-
-
    -
  1. -

    If the data is invalid (eg duplicate email address), our UI will remain in the editing mode and show error messages on the invalid fields. -This allows the user to correct the errors and try saving again.

    -
  2. -
  3. -

    If the data is valid, our backend will persist the edits, and our UI will switch back to a display mode (the contact details UI).

    -
  4. -
-
-
-

So our backend needs to distinguish between a valid and invalid edit. -To support these two scenarios, let’s make some changes to the existing contacts_edit_post() view in the Flask app.

-
-
-
app.py
-
-
@app.route("/contacts/<contact_id>/edit", methods=["POST"])
+                    
+
+
+
    +
  1. +

    Text input holding the current value for the contact’s first name

    +
  2. +
  3. +

    Text element that could display errors from the contact model

    +
  4. +
  5. +

    Another text field, this time for the contact’s last name

    +
  6. +
+
+
+

I omitted the code for the contact’s phone number and email address, because they follow + the same pattern as the first and last name. + Each contact field has its own <text-field>, and a <text> + element below it to display possible errors. + The <text-field> has two important attributes:

+
+
+
    +
  • +

    name defines the name to use when serializing the text-field’s value + into form data for HTTP requests. + We are using the same names as the web app from previous chapters + (first_name, last_name, phone, + email). + That way, we don’t need to make changes in our backend to parse the form data.

    +
  • +
  • +

    value defines the pre-filled data in the text field. + Since we are editing an existing contact, it makes sense to pre-fill the text field with + the current name, phone, or email.

    +
  • +
+
+
+

You might be wondering, why did we choose to define the form fields in a separate template + (form_fields.xml)? + To understand that decision, we need to first discuss the “Save” button. + When pressed, the Hyperview client will make an HTTP POST request to + contacts/<contact_id>/edit, with form data serialized from the + <text-field> inputs. + The HXML response will replace the contents of form field container (ID + form-fields). + But what should that response be? + That depends on the validity of the form data:

+
+
+
    +
  1. +

    If the data is invalid (eg duplicate email address), our UI will remain in the editing + mode and show error messages on the invalid fields. + This allows the user to correct the errors and try saving again.

    +
  2. +
  3. +

    If the data is valid, our backend will persist the edits, and our UI will switch back to + a display mode (the contact details UI).

    +
  4. +
+
+
+

So our backend needs to distinguish between a valid and invalid edit. + To support these two scenarios, let’s make some changes to the existing + contacts_edit_post() view in the Flask app.

+
+
+
app.py
+
+
@app.route("/contacts/<contact_id>/edit", methods=["POST"])
 def contacts_edit_post(contact_id=0):
     c = Contact.find(contact_id)
     c.update(request.form['first_name'], request.form['last_name'], request.form['phone'], request.form['email']) (1)
@@ -16137,36 +22694,42 @@ def contacts_edit_post(contact_id=0):
         return render_to_response("hv/form_fields.xml", contact=c, saved=True) (3)
     else:
         return render_to_response("hv/form_fields.xml", contact=c) (4)
-
-
-
-
    -
  1. -

    Update the contact object from the request’s form data.

    -
  2. -
  3. -

    Attempt to persist the updates. This returns False for invalid data.

    -
  4. -
  5. -

    On success, render the form fields template, and pass a saved flag to the template

    -
  6. -
  7. -

    On failure, render the form fields template. Error messages are present on the contact object.

    -
  8. -
-
-
-

This view already contains conditional logic based on whether the contact model save() succeeds. -If save() fails, we render the form_fields.xml template. -contact.errors will contain error messages for the invalid fields, which will be rendered into the <text style="edit-field-error"> elements. -If save() succeeds, we will also render the form_fields.xml template. -But this time, the template will get a saved flag, indicating success. -We will update the template to use this flag to implement our desired UI: switching the UI back to display mode.

-
-
-
hv/form_fields.xml
-
-
<view style="edit-group">
+                    
+
+
+
    +
  1. +

    Update the contact object from the request’s form data.

    +
  2. +
  3. +

    Attempt to persist the updates. This returns False for invalid data.

    +
  4. +
  5. +

    On success, render the form fields template, and pass a saved flag to the + template

    +
  6. +
  7. +

    On failure, render the form fields template. Error messages are present on the contact + object.

    +
  8. +
+
+
+

This view already contains conditional logic based on whether the contact model + save() succeeds. + If save() fails, we render the form_fields.xml template. + contact.errors will contain error messages for the invalid fields, which will be + rendered into the <text style="edit-field-error"> elements. + If save() succeeds, we will also render the form_fields.xml template. + But this time, the template will get a saved flag, indicating success. + We will update the template to use this flag to implement our desired UI: switching the UI back + to display mode. +

+
+
+
hv/form_fields.xml
+
+
<view style="edit-group">
   {% if saved %} (1)
     <behavior
       trigger="load" (2)
@@ -16182,71 +22745,85 @@ We will update the template to use this flag to implement our desired UI: switch
 
   <!-- same markup for the other fields -->
 </view>
-
-
-
-
    -
  1. -

    Only include this behavior after successfully saving a contact.

    -
  2. -
  3. -

    Trigger the behavior immediately

    -
  4. -
  5. -

    The behavior will reload the entire screen

    -
  6. -
  7. -

    The screen will be reloaded with the contact details screen.

    -
  8. -
-
-
-

The Jinja template conditional ensures that our behavior only renders on successful saves, and not when the screen first opens (or the user submits invalid data). -On success, the template includes a behavior that triggers immediately thanks to trigger="load". -The action reloads the current screen with the Contact Details screen (from the /contacts/<contact_id> route). -The result? -When the user hits “Save”, our backend persists the new contact data, and the screen switches back to the Details screen. -Since the app will make a new HTTP request to get the contact details, it’s guaranteed to show the freshly saved edits.

-
- -
-

We now have a working Edit UI in our contacts app. -Users can enter the Edit mode by pressing a button on the contact details screen. -In the Edit mode, they can update the contact’s data and save it to the backend. -If the backend rejects the edits as invalid, the app stays in Edit mode and shows the validation errors. -If the backend accepts and persists the edits, the app will switch back to the details mode, showing the updated contact data.

-
-
-

Let’s add one more enhancement to the Edit UI. -It would be nice to let the user switch away from the Edit mode without needing to save the contact. -This is typically done by providing a “Cancel” action. -We can add this as a new button below the “Save” button.

-
-
-
Snippet of hv/edit.xml
-
-
<view style="button">
+                    
+
+
+
    +
  1. +

    Only include this behavior after successfully saving a contact.

    +
  2. +
  3. +

    Trigger the behavior immediately

    +
  4. +
  5. +

    The behavior will reload the entire screen

    +
  6. +
  7. +

    The screen will be reloaded with the contact details screen.

    +
  8. +
+
+
+

The Jinja template conditional ensures that our behavior only renders on successful saves, and + not when the screen first opens (or the user submits invalid data). + On success, the template includes a behavior that triggers immediately thanks to + trigger="load". + The action reloads the current screen with the Contact Details screen (from the + /contacts/<contact_id> route). + The result? + When the user hits “Save”, our backend persists the new contact data, and the screen + switches back to the Details screen. + Since the app will make a new HTTP request to get the contact details, it’s guaranteed to + show the freshly saved edits.

+
+ +
+

We now have a working Edit UI in our contacts app. + Users can enter the Edit mode by pressing a button on the contact details screen. + In the Edit mode, they can update the contact’s data and save it to the backend. + If the backend rejects the edits as invalid, the app stays in Edit mode and shows the validation + errors. + If the backend accepts and persists the edits, the app will switch back to the details mode, + showing the updated contact data.

+
+
+

Let’s add one more enhancement to the Edit UI. + It would be nice to let the user switch away from the Edit mode without needing to save the + contact. + This is typically done by providing a “Cancel” action. + We can add this as a new button below the “Save” button.

+
+
+
Snippet of hv/edit.xml
+
+
<view style="button">
   <behavior trigger="press" action="replace-inner" target="form-fields" href="/contacts/{{contact.id}}/edit" verb="post" />
   <text style="button-label">Save</text>
 </view>
@@ -16258,142 +22835,170 @@ We can add this as a new button below the “Save” button.

/> <text style="button-label">Cancel</text> </view>
-
-
-
-
    -
  1. -

    New Cancel button on the edit screen

    -
  2. -
  3. -

    When pressed, reload the entire screen

    -
  4. -
  5. -

    The screen will be reloaded with the contact details screen.

    -
  6. -
-
-
-

This is the same technique we used to switch from the edit UI to the details UI upon successfully editing the contact. -But pressing “Cancel” will update the UI faster than pressing “Save”. -On save, the app will first make a POST request to save the data, and then a GET request for the details screen. -Cancelling skips the POST, and immediately makes the GET request.

-
-
-
-screenshot hyperview edit -
-
Figure 15. Contact Edit Screen
-
-
-

Updating the Contacts List

-
-

At this point, we can claim to have fully implemented the Edit UI. -But there’s a problem. -In fact, if we stopped here, users may even consider the app to be buggy! -Why? -It has to do with syncing the app state across multiple screens. -Let’s walk through this series of interactions:

-
-
-
    -
  1. -

    Launch the app to the Contacts List.

    -
  2. -
  3. -

    Press on the contact “Joe Blow” to load his Contact Details.

    -
  4. -
  5. -

    Press Edit to switch to the edit mode, and change the contact’s first name to “Joseph”.

    -
  6. -
  7. -

    Press Save to switch back to viewing mode. The contact’s name is now “Joseph Blow”.

    -
  8. -
  9. -

    Hit the back button to return to the Contacts List.

    -
  10. -
-
-
-

Did you catch the issue? -Our Contacts list is still showing the same list of names as when we launched the app. -The contact we just renamed to “Joseph” is still showing up in the list as “Joe”. -This is a general problem in Hypermedia applications. -The client does not have a notion of shared data across different parts of the UI. -Updates in one part of the app will not automatically update other parts of the app. -Luckily, there’s a solution to this problem in Hyperview: events. -Events are built into the behavior system, and allow lightweight communication between different parts of the UI.

-
- -
-

We know enough about Hyperview’s event system to solve the bug in our app. -When the user saves a change to a contact, we need to dispatch an event from the Details screen. -And the Contacts screen needs to listen to that event, and reload itself to reflect the edits. -Since the form_fields.xml template already gets the saved flag when the backend successfully saves a contact, it’s a good place to dispatch the event:

-
-
-
Snippet from hv/form_fields.xml
-
-
{% if saved %}
+                    
+
+
+
    +
  1. +

    New Cancel button on the edit screen

    +
  2. +
  3. +

    When pressed, reload the entire screen

    +
  4. +
  5. +

    The screen will be reloaded with the contact details screen.

    +
  6. +
+
+
+

This is the same technique we used to switch from the edit UI to the details UI upon successfully + editing the contact. + But pressing “Cancel” will update the UI faster than pressing “Save”. + On save, the app will first make a POST request to save the data, and then a + GET request for the details screen. + Cancelling skips the POST, and immediately makes the GET request.

+
+
+
+ screenshot hyperview edit +
+
Figure 15. Contact Edit Screen
+
+
+

Updating the Contacts List

+
+

At this point, we can claim to have fully implemented the Edit UI. + But there’s a problem. + In fact, if we stopped here, users may even consider the app to be buggy! + Why? + It has to do with syncing the app state across multiple screens. + Let’s walk through this series of interactions:

+
+
+
    +
  1. +

    Launch the app to the Contacts List.

    +
  2. +
  3. +

    Press on the contact “Joe Blow” to load his Contact Details.

    +
  4. +
  5. +

    Press Edit to switch to the edit mode, and change the contact’s first name to + “Joseph”.

    +
  6. +
  7. +

    Press Save to switch back to viewing mode. The contact’s name is now + “Joseph Blow”.

    +
  8. +
  9. +

    Hit the back button to return to the Contacts List.

    +
  10. +
+
+
+

Did you catch the issue? + Our Contacts list is still showing the same list of names as when we launched the app. + The contact we just renamed to “Joseph” is still showing up in the list as + “Joe”. + This is a general problem in Hypermedia applications. + The client does not have a notion of shared data across different parts of the UI. + Updates in one part of the app will not automatically update other parts of the app. + Luckily, there’s a solution to this problem in Hyperview: events. + Events are built into the behavior system, and allow lightweight communication between + different parts of the UI.

+
+ +
+

We know enough about Hyperview’s event system to solve the bug in our app. + When the user saves a change to a contact, we need to dispatch an event from the Details + screen. + And the Contacts screen needs to listen to that event, and reload itself to reflect the + edits. + Since the form_fields.xml template already gets the saved flag + when the backend successfully saves a contact, it’s a good place to dispatch the + event:

+
+
+
Snippet from hv/form_fields.xml
+
+
{% if saved %}
   <behavior
     trigger="load" (1)
     action="dispatch-event" (2)
@@ -16405,31 +23010,32 @@ Since the form_fields.xml template already gets the saved
-
-
-
-
    -
  1. -

    Trigger the behavior immediately

    -
  2. -
  3. -

    The behavior will dispatch an event

    -
  4. -
  5. -

    The event name is "contact-updated"

    -
  6. -
  7. -

    The existing behavior to show the Details UI.

    -
  8. -
-
-
-

Now, we just need the contacts list to listen for the contact-updated event, and reload itself:

-
-
-
Snippet from hv/index.xml
-
-
<form>
+                        
+
+
+
    +
  1. +

    Trigger the behavior immediately

    +
  2. +
  3. +

    The behavior will dispatch an event

    +
  4. +
  5. +

    The event name is "contact-updated"

    +
  6. +
  7. +

    The existing behavior to show the Details UI.

    +
  8. +
+
+
+

Now, we just need the contacts list to listen for the contact-updated event, and + reload itself:

+
+
+
Snippet from hv/index.xml
+
+
<form>
   <behavior
     trigger="on-event" (1)
     event-name="contact-updated" (2)
@@ -16443,54 +23049,68 @@ Since the form_fields.xml template already gets the saved
-
-
-
-
    -
  1. -

    Trigger the behavior on event dispatch

    -
  2. -
  3. -

    Trigger the behavior for dispatched events with the name “contact-updated”

    -
  4. -
  5. -

    When triggered, replace the contents of the <list> element with rows from the backend

    -
  6. -
-
-
-

Any time the user edits a contact, the Contacts List screen will update to reflect the edits. -The addition of these two <behavior> elements fixes the bug: the Contacts List screen will correctly show “Joseph Blow” in the list. -Note that we intentionally added the new behavior inside the <form> element. -The ensures the triggered request will preserve any search query. -To show what we mean, let’s revisit the set of steps that demonstrated the buggy behavior. -Assume that before pressing on “Joe Blow”, the user had searched the contacts by typing “Joe” in the search field. -When the user later updates the contact to “Joseph Blow”, our template dispatches the “contact-updated” event, which triggers the replace-inner behavior on the contact list screen. -Due to the parent <form> element, the search query “Joe” will be serialized with the request: GET /contacts?rows_only=true&q=Joe. -Since the name “Joseph” doesn’t match the query “Joe”, the contact we edited will not appear in the list (until the user clears out the query). -Our app’s state remains consistent across our backend and all active screens.

-
-
-

Events introduce a level of abstraction to behaviors. -So far, we’ve seen that editing a contact will cause the list of contacts to refresh. -But the list of contacts should also refresh after other actions, such as deleting a contact or adding a new contact. -As long as our HXML responses for deletion or creation include a behavior to dispatch a contact-updated event, then we will get the desired refresh behavior on the contacts list screen. -The screen doesn’t care what causes the contact-updated event to be dispatched. -It just knows what it needs to do when it happens.

-
-
-
-
-

13.4. Deleting a Contact

-
-

Speaking of deleting a contact, this is a good next feature to implement. -We will let users delete a contact from the Edit UI. -So let’s add a new button to edit.xml.

-
-
-
Snippet of hv/edit.xml
-
-
<view style="button">
+                        
+
+
+
    +
  1. +

    Trigger the behavior on event dispatch

    +
  2. +
  3. +

    Trigger the behavior for dispatched events with the name + “contact-updated”

    +
  4. +
  5. +

    When triggered, replace the contents of the <list> element with + rows from the backend

    +
  6. +
+
+
+

Any time the user edits a contact, the Contacts List screen will update to reflect the edits. + The addition of these two <behavior> elements fixes the bug: the Contacts + List screen will correctly show “Joseph Blow” in the list. + Note that we intentionally added the new behavior inside the <form> + element. + The ensures the triggered request will preserve any search query. + To show what we mean, let’s revisit the set of steps that demonstrated the buggy + behavior. + Assume that before pressing on “Joe Blow”, the user had searched the contacts by + typing “Joe” in the search field. + When the user later updates the contact to “Joseph Blow”, our template + dispatches the “contact-updated” event, which triggers the + replace-inner behavior on the contact list screen. + Due to the parent <form> element, the search query “Joe” will + be serialized with the request: GET /contacts?rows_only=true&q=Joe. + Since the name “Joseph” doesn’t match the query “Joe”, the + contact we edited will not appear in the list (until the user clears out the query). + Our app’s state remains consistent across our backend and all active screens.

+
+
+

Events introduce a level of abstraction to behaviors. + So far, we’ve seen that editing a contact will cause the list of contacts to refresh. + But the list of contacts should also refresh after other actions, such as deleting a contact + or adding a new contact. + As long as our HXML responses for deletion or creation include a behavior to dispatch a + contact-updated event, then we will get the desired refresh behavior on the + contacts list screen. + The screen doesn’t care what causes the contact-updated event to be + dispatched. + It just knows what it needs to do when it happens.

+
+
+
+
+

13.4. Deleting a Contact

+
+

Speaking of deleting a contact, this is a good next feature to implement. + We will let users delete a contact from the Edit UI. + So let’s add a new button to edit.xml.

+
+
+
Snippet of hv/edit.xml
+
+
<view style="button">
   <behavior trigger="press" action="replace-inner" target="form-fields" href="/contacts/{{contact.id}}/edit" verb="post" />
   <text style="button-label">Save</text>
 </view>
@@ -16508,76 +23128,95 @@ So let’s add a new button to edit.xml.

/> <text style="button-label button-label-delete">Delete Contact</text> </view>
-
-
-
-
    -
  1. -

    New Delete Contact button on the edit screen

    -
  2. -
  3. -

    When pressed, append HXML to a container on the screen

    -
  4. -
  5. -

    The HXML will be fetched by making a POST /contacts/<contact_id>/delete request

    -
  6. -
-
-
-

The HXML for the Delete button is pretty similar to the Save button, but there are a few subtle differences. -Remember, pressing the Save button results in one of two expected outcomes: failing and showing validation errors on the form, or succeeding and switching to the contact details screen. -To support the first outcome (failing and showing validation errors), the save behavior replaces the contents of the <view id="form-fields"> container with a re-rendered version of form_fields.xml. -Therefore, using the replace-inner action makes sense.

-
-
-

Deletion does not involve a validation step, so there’s only one expected outcome: successfully deleting the contact. -When deletion succeeds, the contact no longer exists. -It doesn’t make sense to show the edit UI or contact details for a non-existent contact. -Instead, our app will navigate back to the previous screen (the contacts list). -Our response will only include behaviors that trigger immediately, there’s no UI to change. -Therefore, using the append action will preserve the current UI while Hyperview runs the actions.

-
-
-
Snippet of hv/deleted.xml
-
-
<view>
+                    
+
+
+
    +
  1. +

    New Delete Contact button on the edit screen

    +
  2. +
  3. +

    When pressed, append HXML to a container on the screen

    +
  4. +
  5. +

    The HXML will be fetched by making a + POST /contacts/<contact_id>/delete request

    +
  6. +
+
+
+

The HXML for the Delete button is pretty similar to the Save button, but there are a few subtle + differences. + Remember, pressing the Save button results in one of two expected outcomes: failing and showing + validation errors on the form, or succeeding and switching to the contact details screen. + To support the first outcome (failing and showing validation errors), the save behavior replaces + the contents of the <view id="form-fields"> container with a re-rendered + version of form_fields.xml. + Therefore, using the replace-inner action makes sense.

+
+
+

Deletion does not involve a validation step, so there’s only one expected outcome: + successfully deleting the contact. + When deletion succeeds, the contact no longer exists. + It doesn’t make sense to show the edit UI or contact details for a non-existent contact. + Instead, our app will navigate back to the previous screen (the contacts list). + Our response will only include behaviors that trigger immediately, there’s no UI to + change. + Therefore, using the append action will preserve the current UI while Hyperview + runs the actions.

+
+
+
Snippet of hv/deleted.xml
+
+
<view>
   <behavior trigger="load" action="dispatch-event" event-name="contact-updated" /> (1)
   <behavior trigger="load" action="back" /> (2)
 </view>
-
-
-
-
    -
  1. -

    On load, dispatch the contact-updated event to update the contact lists screen

    -
  2. -
  3. -

    Navigate back to the contacts list screen.

    -
  4. -
-
-
-

Note that in addition to behavior to navigate back, this template also includes a behavior to dispatch the contact-updated event. -In the previous chapter section, we added a behavior to index.xml to refresh the list when that event is dispatched. -By dispatching the event after a deletion, we will make sure the deleted contact gets removed from the list.

-
-
-

Once again, I’m going to skip over the changes to the Flask backend. -Suffice it to say, we will need to update the contacts_delete() view to respond with the hv/deleted.xml template. -And we need to update the route to support POST in addition to DELETE, since the Hyperview client only understands GET and POST.

-
-
-

We now have a fully functioning deletion feature! -But it’s not the most user-friendly: it takes one accidental tap to permanently delete a contact. -For destructive actions like deleting a contact, it’s always a good idea to ask the user for confirmation. -We can add a confirmation to the delete behavior by using the alert system action described in the previous chapter. -As you recall, the alert action will show a system dialog box with buttons that can trigger other behaviors. -All we have to do is wrap the delete <behavior> in a behavior that uses action="alert".

-
-
-
Delete button in hv/edit.xml
-
-
<view style="button">
+                    
+
+
+
    +
  1. +

    On load, dispatch the contact-updated event to update the contact lists + screen

    +
  2. +
  3. +

    Navigate back to the contacts list screen.

    +
  4. +
+
+
+

Note that in addition to behavior to navigate back, this template also includes a behavior to + dispatch the contact-updated event. + In the previous chapter section, we added a behavior to index.xml to refresh the + list when that event is dispatched. + By dispatching the event after a deletion, we will make sure the deleted contact gets removed + from the list.

+
+
+

Once again, I’m going to skip over the changes to the Flask backend. + Suffice it to say, we will need to update the contacts_delete() view to respond + with the hv/deleted.xml template. + And we need to update the route to support POST in addition to DELETE, + since the Hyperview client only understands GET and POST.

+
+
+

We now have a fully functioning deletion feature! + But it’s not the most user-friendly: it takes one accidental tap to permanently delete a + contact. + For destructive actions like deleting a contact, it’s always a good idea to ask the user + for confirmation. + We can add a confirmation to the delete behavior by using the alert system action + described in the previous chapter. + As you recall, the alert action will show a system dialog box with buttons that can + trigger other behaviors. + All we have to do is wrap the delete <behavior> in a behavior that uses + action="alert".

+
+
+
Delete button in hv/edit.xml
+
+
<view style="button">
   <behavior (1)
     xmlns:alert="https://hyperview.org/hyperview-alert"
     trigger="press"
@@ -16598,283 +23237,351 @@ All we have to do is wrap the delete <behavior> in a behavior
   </behavior>
   <text style="button-label button-label-delete">Delete Contact</text>
 </view>
-
-
-
-
    -
  1. -

    Pressing "Delete" triggers an action to show the system dialog with the given title and message.

    -
  2. -
  3. -

    The first pressable option in the system dialog

    -
  4. -
  5. -

    Pressing the first option will trigger contact deletion

    -
  6. -
  7. -

    The second pressable option has no behavior, so it only closes the dialog.

    -
  8. -
-
-
-

Unlike before, pressing the delete button will not have an immediate effect. -Instead, the user will be presented with the dialog box and asked to confirm or cancel. -Our core deletion behavior didn’t change, we just chained it from another behavior.

-
-
-
-screenshot hyperview delete cropped -
-
Figure 16. Delete Contact confirmation
-
-
-
-

13.5. Adding a New Contact

-
-

Adding a new contact is the last feature we want to support in our mobile app. -And luckily, it’s also the easiest. -We can reuse the concepts (and even some templates) from features we’ve already implemented. -In particular, adding a new contact is very similar to editing an existing contact. -Both features need to:

-
-
-
    -
  • -

    Show a form to collect information about the contact

    -
  • -
  • -

    Have a way to save the entered information

    -
  • -
  • -

    Show validation errors on the form

    -
  • -
  • -

    Persist the contact when there are no validation errors

    -
  • -
-
-
-

Since the functionality is so similar, I’m going to summarize the changes here without showing the code. -Hopefully, you can follow along:

-
-
-
    -
  1. -

    Update index.xml.

    -
    -
      -
    • -

      Override the header block to add a new “Add” button.

      -
    • -
    • -

      Include a behavior in the button. When pressed, push a new screen as a modal by using action="new", and request the screen content from /contacts/new.

      -
    • -
    -
    -
  2. -
  3. -

    Create a template hv/new.xml.

    -
    -
      -
    • -

      Override the header block to include a button that closes the modal, using action="close".

      -
    • -
    • -

      Include the hv/form_fields.xml template to render empty form fields

      -
    • -
    • -

      Add a “Add Contact” button below the form fields.

      -
    • -
    • -

      Include a behavior in the button. When pressed, make a POST request to /contacts/new, and use action="replace-inner" to update the form fields.

      -
    • -
    -
    -
  4. -
  5. -

    Update the Flask view.

    -
    -
      -
    • -

      Change contacts_new_get() to use render_to_response() with the hv/new.xml template.

      -
    • -
    • -

      Change contacts_new() to use render_to_response() with the hv/form_fields.xml template. Pass saved=True when rendering the template after successfully persisting the new contact.

      -
    • -
    -
    -
  6. -
-
-
-

By reusing form_fields.xml for both editing and adding a contact, we get to reuse some code and ensure the two features have a consistent UI. -Also, our “Add Contact” screen will benefit from the “saved” logic that’s already a part of form_fields.xml. -After successfully adding a new contact, the screen will dispatch the contact-updated event, which will refresh the contacts list and show the newly added contact. -The screen will reload itself to show the Contact Details.

-
-
-
-screenshot hyperview add -
-
Figure 17. Add Contact modal
-
-
-
-

13.6. Deploying the App

-
-

With the completion of the contact creation UI, we have a fully implemented mobile app! -It supports searching a list of contacts, viewing the details of a contact, editing and deleting a contact, and adding a new contact. -But so far, we’ve been developing the app using a simulator on our desktop computer. -How can we see it running on a mobile device? -And how can we get it into the hands of our users?

-
-
-

To see the app running on a physical device, let’s take advantage of the Expo platform’s app preview functionality.

-
-
-
    -
  1. -

    Download the Expo Go app on an Android or iOS device.

    -
  2. -
  3. -

    Restart the Flask app, binding to an interface accessible on your network. -This might look something like flask run --host 192.168.7.229, where the host is your computer’s IP address on the network.

    -
  4. -
  5. -

    Update the Hyperview client code so that ENTRY_POINT_URL (in demo/src/constants.js) points to the IP and port that the Flask server is bound to.

    -
  6. -
  7. -

    After running yarn start in the Hyperview demo app, you will see a QR code printed in the console, with instructions on how to scan it on Android and iOS.

    -
  8. -
-
-
-

Once you scan the QR code, the full app will run on the device! -As you interact with the app, you will see HTTP requests made to the Flask server. -You can even use the physical device during development. -Any time you make a change in the HXML, just reload the screen to see the UI updates.

-
-
-

So we have the app running on a physical device, but it’s still not production ready. -To get the app into the hands of our users, there’s a few things we need to do:

-
-
-
    -
  1. -

    Deploy our backend in production. -We need to use a production-grade web server like Gunicorn instead of the Flask development server. -And we should run our app on a machine reachable on the Internet, most likely using a cloud provider like AWS or Heroku.

    -
  2. -
  3. -

    Create standalone binary apps. -By following the instructions from the Expo project, we can create a .ipa or .apk file, for the iOS and Android platforms. -Remember to update ENTRY_POINT_URL in the Hyperview client to point to the production backend.

    -
  4. -
  5. -

    Submit our binaries to the iOS App Store or Google Play Store, and wait for app approval.

    -
  6. -
-
-
-

Once the app is approved, congratulations! -Our mobile app can be downloaded by Android and iOS users. -And here’s the best part: -Because our app uses the hypermedia architecture, we can add features to our app by simply updating the backend. -The UI and interactions are completely specified with the HXML generated from server-side templates. -Want to add a new section to a screen? -Just update an existing HXML template. -Want to add a new type of screen to the app? -Create a new route, view, and HXML template. -Then, add a behavior to an existing screen that will open the new screen. -To push these changes to your users, you just need to re-deploy the backend. -Our app knows how to interpret HXML, and that’s enough for it to understand how to handle the new features.

-
-
-
-

13.7. One Backend, Multiple Hypermedia formats

-
-

To create a mobile app using the hypermedia architecture, we started with the web-based contacts app and made a few changes, primarily replacing HTML templates with HXML templates. -But in the process of porting the backend to serve our mobile app, we lost the web application functionality. -Indeed, if you tried to visit http://0.0.0.0:5000 in a web browser, you would see a jumble of text and XML markup. -That’s because web browsers don’t know how to render plain XML, and they certainly don’t know how to interpret the tags and attributes of HXML to render an app. -It’s a shame, because the Flask code for the web application and mobile app are nearly identical. -The database and model logic are shared, and most of the views are unchanged as well.

-
-
-

At this point you’re surely wondering: is it possible to use the same backend to serve both a web application and mobile app? -The answer is yes! -In fact, this is one of the benefits of using a hypermedia architecture across multiple platforms. -We don’t need to port any client-side logic from one platform to another, we just need to respond to requests with the appropriate Hypermedia format. -To do this, we will utilize content negotiation built into HTTP.

-
-
-

What is Content Negotiation?

-
-

Imagine a German speaker and Japanese speaker both visit https://google.com in their web browser. -They will see the Google home page localized in German and Japanese, respectively. -How does Google know to return a different version of the homepage based on the user’s preferred language? -The answer lies in the REST architecture, and how it separates the concepts of resources and representations.

-
-
-

In the REST architecture, the Google homepage is considered to be a single “resource”, represented by a unique URL. -However, that single resource can have multiple “representations”. -Representations are variations on how the content of the resource is presented to the client. -The German and Japanese versions of the Google homepage are two representations of the same resource. -To determine the best representation of a resource to return, HTTP clients and servers engage in a process called “content negotiation”. -It works like this:

-
-
-
    -
  • -

    Clients specify the preferred representation through Accept-* request headers.

    -
  • -
  • -

    The server tries to match the preferred representation as best it can, and communicates back the chosen representation using Content-*.

    -
  • -
-
-
-

In the Google homepage example, the German speaker uses a browser that is set to prefer content localized for German. -Every HTTP request made by the web browser will include a header Accept-Language: de-DE. -The server sees the request header, and it will return a response localized for German (if it can). -The HTTP response will include a Content-Language: de-DE header to inform the client of the language of the response content.

-
-
-

Language is just one factor for resource representation. -More importantly for us, resources can be represented using different content types, such as HTML or HXML. -Content negotiation over content type is done using the Accept request header and Content-Type response header. -Web browsers set text/html as the preferred content type in the Accept header. -The Hyperview client sets application/vnd.hyperview+xml as the preferred content type. -This gives our backend a way to distinguish requests coming from a web browser or Hyperview client, and serve the appropriate content to each. -There are two main approaches: fine-grained and global.

-
-
-
-

Approach 1: Template Switching

-
-

When we ported the Contacts app from the web to mobile, we kept all of the Flask views but made some minor changes. -Specifically, we introduced a new function render_to_response() and called it in the return statement of each view. -Here’s the function again to refresh your memory:

-
-
-
app.py
-
-
def render_to_response(template_name, *args, **kwargs):
+                    
+
+
+
    +
  1. +

    Pressing "Delete" triggers an action to show the system dialog with the given title and + message.

    +
  2. +
  3. +

    The first pressable option in the system dialog

    +
  4. +
  5. +

    Pressing the first option will trigger contact deletion

    +
  6. +
  7. +

    The second pressable option has no behavior, so it only closes the dialog.

    +
  8. +
+
+
+

Unlike before, pressing the delete button will not have an immediate effect. + Instead, the user will be presented with the dialog box and asked to confirm or cancel. + Our core deletion behavior didn’t change, we just chained it from another behavior.

+
+
+
+ screenshot hyperview delete cropped +
+
Figure 16. Delete Contact confirmation
+
+
+
+

13.5. Adding a New Contact

+
+

Adding a new contact is the last feature we want to support in our mobile app. + And luckily, it’s also the easiest. + We can reuse the concepts (and even some templates) from features we’ve already + implemented. + In particular, adding a new contact is very similar to editing an existing contact. + Both features need to:

+
+
+
    +
  • +

    Show a form to collect information about the contact

    +
  • +
  • +

    Have a way to save the entered information

    +
  • +
  • +

    Show validation errors on the form

    +
  • +
  • +

    Persist the contact when there are no validation errors

    +
  • +
+
+
+

Since the functionality is so similar, I’m going to summarize the changes here without + showing the code. + Hopefully, you can follow along:

+
+
+
    +
  1. +

    Update index.xml.

    +
    +
      +
    • +

      Override the header block to add a new “Add” button. +

      +
    • +
    • +

      Include a behavior in the button. When pressed, push a new screen as a modal + by using action="new", and request the screen content from + /contacts/new.

      +
    • +
    +
    +
  2. +
  3. +

    Create a template hv/new.xml.

    +
    +
      +
    • +

      Override the header block to include a button that closes the modal, using + action="close".

      +
    • +
    • +

      Include the hv/form_fields.xml template to render empty form + fields

      +
    • +
    • +

      Add a “Add Contact” button below the form fields.

      +
    • +
    • +

      Include a behavior in the button. When pressed, make a POST + request to /contacts/new, and use + action="replace-inner" to update the form fields.

      +
    • +
    +
    +
  4. +
  5. +

    Update the Flask view.

    +
    +
      +
    • +

      Change contacts_new_get() to use + render_to_response() with the hv/new.xml template. +

      +
    • +
    • +

      Change contacts_new() to use render_to_response() + with the hv/form_fields.xml template. Pass + saved=True when rendering the template after successfully + persisting the new contact.

      +
    • +
    +
    +
  6. +
+
+
+

By reusing form_fields.xml for both editing and adding a contact, we get to reuse + some code and ensure the two features have a consistent UI. + Also, our “Add Contact” screen will benefit from the “saved” logic + that’s already a part of form_fields.xml. + After successfully adding a new contact, the screen will dispatch the + contact-updated event, which will refresh the contacts list and show the newly + added contact. + The screen will reload itself to show the Contact Details.

+
+
+
+ screenshot hyperview add +
+
Figure 17. Add Contact modal
+
+
+
+

13.6. Deploying the App

+
+

With the completion of the contact creation UI, we have a fully implemented mobile app! + It supports searching a list of contacts, viewing the details of a contact, editing and deleting + a contact, and adding a new contact. + But so far, we’ve been developing the app using a simulator on our desktop computer. + How can we see it running on a mobile device? + And how can we get it into the hands of our users?

+
+
+

To see the app running on a physical device, let’s take advantage of the Expo + platform’s app preview functionality.

+
+
+
    +
  1. +

    Download the Expo Go app on an Android or iOS device.

    +
  2. +
  3. +

    Restart the Flask app, binding to an interface accessible on your network. + This might look something like flask run --host 192.168.7.229, where the + host is your computer’s IP address on the network.

    +
  4. +
  5. +

    Update the Hyperview client code so that ENTRY_POINT_URL (in + demo/src/constants.js) points to the IP and port that the Flask server is + bound to.

    +
  6. +
  7. +

    After running yarn start in the Hyperview demo app, you will see a QR code + printed in the console, with instructions on how to scan it on Android and iOS.

    +
  8. +
+
+
+

Once you scan the QR code, the full app will run on the device! + As you interact with the app, you will see HTTP requests made to the Flask server. + You can even use the physical device during development. + Any time you make a change in the HXML, just reload the screen to see the UI updates.

+
+
+

So we have the app running on a physical device, but it’s still not production ready. + To get the app into the hands of our users, there’s a few things we need to do:

+
+
+
    +
  1. +

    Deploy our backend in production. + We need to use a production-grade web server like Gunicorn instead of the Flask + development server. + And we should run our app on a machine reachable on the Internet, most likely using a + cloud provider like AWS or Heroku.

    +
  2. +
  3. +

    Create standalone binary apps. + By following the instructions from the Expo project, we can create a .ipa + or .apk file, for the iOS and Android platforms. + Remember to update ENTRY_POINT_URL in the Hyperview client to point to the + production backend.

    +
  4. +
  5. +

    Submit our binaries to the iOS App Store or Google Play Store, and wait for app approval. +

    +
  6. +
+
+
+

Once the app is approved, congratulations! + Our mobile app can be downloaded by Android and iOS users. + And here’s the best part: + Because our app uses the hypermedia architecture, we can add features to our app by simply + updating the backend. + The UI and interactions are completely specified with the HXML generated from server-side + templates. + Want to add a new section to a screen? + Just update an existing HXML template. + Want to add a new type of screen to the app? + Create a new route, view, and HXML template. + Then, add a behavior to an existing screen that will open the new screen. + To push these changes to your users, you just need to re-deploy the backend. + Our app knows how to interpret HXML, and that’s enough for it to understand how to handle + the new features.

+
+
+
+

13.7. One Backend, Multiple Hypermedia formats

+
+

To create a mobile app using the hypermedia architecture, we started with the web-based contacts + app and made a few changes, primarily replacing HTML templates with HXML templates. + But in the process of porting the backend to serve our mobile app, we lost the web application + functionality. + Indeed, if you tried to visit + http://0.0.0.0:5000 in a web + browser, you would see a jumble of text and XML markup. + That’s because web browsers don’t know how to render plain XML, and they certainly + don’t know how to interpret the tags and attributes of HXML to render an app. + It’s a shame, because the Flask code for the web application and mobile app are nearly + identical. + The database and model logic are shared, and most of the views are unchanged as well.

+
+
+

At this point you’re surely wondering: is it possible to use the same backend to serve both + a web application and mobile app? + The answer is yes! + In fact, this is one of the benefits of using a hypermedia architecture across multiple + platforms. + We don’t need to port any client-side logic from one platform to another, we just need to + respond to requests with the appropriate Hypermedia format. + To do this, we will utilize content negotiation built into HTTP.

+
+
+

What is Content Negotiation?

+
+

Imagine a German speaker and Japanese speaker both visit + https://google.com in their web + browser. + They will see the Google home page localized in German and Japanese, respectively. + How does Google know to return a different version of the homepage based on the user’s + preferred language? + The answer lies in the REST architecture, and how it separates the concepts of resources and + representations.

+
+
+

In the REST architecture, the Google homepage is considered to be a single + “resource”, represented by a unique URL. + However, that single resource can have multiple “representations”. + Representations are variations on how the content of the resource is presented to the + client. + The German and Japanese versions of the Google homepage are two representations of the same + resource. + To determine the best representation of a resource to return, HTTP clients and servers + engage in a process called “content negotiation”. + It works like this:

+
+
+
    +
  • +

    Clients specify the preferred representation through Accept-* request + headers.

    +
  • +
  • +

    The server tries to match the preferred representation as best it can, and + communicates back the chosen representation using Content-*.

    +
  • +
+
+
+

In the Google homepage example, the German speaker uses a browser that is set to prefer + content localized for German. + Every HTTP request made by the web browser will include a header + Accept-Language: de-DE. + The server sees the request header, and it will return a response localized for German (if + it can). + The HTTP response will include a Content-Language: de-DE header to inform the + client of the language of the response content.

+
+
+

Language is just one factor for resource representation. + More importantly for us, resources can be represented using different content types, such as + HTML or HXML. + Content negotiation over content type is done using the Accept request header + and Content-Type response header. + Web browsers set text/html as the preferred content type in the + Accept header. + The Hyperview client sets application/vnd.hyperview+xml as the preferred + content type. + This gives our backend a way to distinguish requests coming from a web browser or Hyperview + client, and serve the appropriate content to each. + There are two main approaches: fine-grained and global.

+
+
+
+

Approach 1: Template Switching

+
+

When we ported the Contacts app from the web to mobile, we kept all of the Flask views but + made some minor changes. + Specifically, we introduced a new function render_to_response() and called it + in the return statement of each view. + Here’s the function again to refresh your memory:

+
+
+
app.py
+
+
def render_to_response(template_name, *args, **kwargs):
     content = render_template(template_name, *args, **kwargs)
     response = make_response(content)
     response.headers['Content-Type'] = 'application/vnd.hyperview+xml'
     return response
-
-
-
-

render_to_response() renders a template with the given context, and turns it into an Flask response object with the appropriate Hyperview Content-Type header. -Obviously, the implementation is highly-specific to serving our Hyperview mobile app. -But we can modify the function to do content negotiation based on the request’s Accept header:

-
-
-
app.py
-
-
HTML_MIME = 'text/html'
+                        
+
+
+

render_to_response() renders a template with the given context, and turns it + into an Flask response object with the appropriate Hyperview Content-Type + header. + Obviously, the implementation is highly-specific to serving our Hyperview mobile app. + But we can modify the function to do content negotiation based on the request’s + Accept header:

+
+
+
app.py
+
+
HTML_MIME = 'text/html'
 HXML_MIME = 'application/vnd.hyperview+xml'
 
 def render_to_response(html_template_name, hxml_template_name, *args, **kwargs): (1)
@@ -16884,89 +23591,105 @@ def render_to_response(html_template_name, hxml_template_name, *args, **kwargs):
     response = make_response(content)
     response.headers['Content-Type'] = response_type (4)
     return response
-
-
-
-
    -
  1. -

    Function signature takes two templates, one for HTML and one for HXML

    -
  2. -
  3. -

    Determine whether the client wants HTML or HXML

    -
  4. -
  5. -

    Select the template based on the best match for the client

    -
  6. -
  7. -

    Set the Content-Type header based on the best match for the client

    -
  8. -
-
-
-

Flask’s request object exposes an accept_mimetypes property to help with content negotiation. -We pass our two content MIME types to request.accept_mimetypes.best_match() and get back the MIME type that works for our client. -Based on the best matching MIME type, we choose to either render an HTML template or HXML template. -We also make sure to set the Content-Type header to the appropriate MIME type. -The only difference in our Flask views is that we need to provide both an HTML and HXML template:

-
-
-
app.py
-
-
@app.route("/contacts/<contact_id>")
+                        
+
+
+
    +
  1. +

    Function signature takes two templates, one for HTML and one for HXML

    +
  2. +
  3. +

    Determine whether the client wants HTML or HXML

    +
  4. +
  5. +

    Select the template based on the best match for the client

    +
  6. +
  7. +

    Set the Content-Type header based on the best match for the client

    +
  8. +
+
+
+

Flask’s request object exposes an accept_mimetypes property to help with + content negotiation. + We pass our two content MIME types to request.accept_mimetypes.best_match() and + get back the MIME type that works for our client. + Based on the best matching MIME type, we choose to either render an HTML template or HXML + template. + We also make sure to set the Content-Type header to the appropriate MIME type. + The only difference in our Flask views is that we need to provide both an HTML and HXML + template:

+
+
+
app.py
+
+
@app.route("/contacts/<contact_id>")
 def contacts_view(contact_id=0):
     contact = Contact.find(contact_id)
     return render_to_response("show.html", "hv/show.xml", contact=contact) (1)
-
-
-
-
    -
  1. -

    Template switching between an HTML and HXML template, based on the client.

    -
  2. -
-
-
-

After updating all of the Flask views to support both templates, our backend will support both web browsers and our mobile app! -This technique works well for the Contacts app because the screens in the mobile app map directly to pages of the web application. -Each app has a dedicated page (or screen) for listing contacts, showing and editing details, and creating a new contact. -This meant the Flask views could be as-is without major changes. -But what if we wanted to re-imagine the Contacts app UI for our mobile app? -Perhaps we want the mobile app to use a single screen, with rows that expanded in-line to support viewing and editing the information? -In situations where the UI diverges between platforms, Template Switching becomes cumbersome or impossible. -We need a different approach to have one backend serve both hypermedia formats.

-
-
-
-

Approach 2: The Redirect Fork

-
-

If you recall, the Contacts web app has an index view, routed from the root path /:

-
-
-
app.py
-
-
@app.route("/")
+                        
+
+
+
    +
  1. +

    Template switching between an HTML and HXML template, based on the client.

    +
  2. +
+
+
+

After updating all of the Flask views to support both templates, our backend will support + both web browsers and our mobile app! + This technique works well for the Contacts app because the screens in the mobile app map + directly to pages of the web application. + Each app has a dedicated page (or screen) for listing contacts, showing and editing details, + and creating a new contact. + This meant the Flask views could be as-is without major changes. + But what if we wanted to re-imagine the Contacts app UI for our mobile app? + Perhaps we want the mobile app to use a single screen, with rows that expanded in-line to + support viewing and editing the information? + In situations where the UI diverges between platforms, Template Switching becomes cumbersome + or impossible. + We need a different approach to have one backend serve both hypermedia formats.

+
+
+
+

Approach 2: The Redirect Fork

+
+

If you recall, the Contacts web app has an index view, routed from the root path + /:

+
+
+
app.py
+
+
@app.route("/")
 def index():
     return redirect("/contacts") (1)
-
-
-
-
    -
  1. -

    Redirect requests from "/" to "/contacts"

    -
  2. -
-
-
-

When someone requests to the root path of the web application, Flask redirects them to the /contacts path. -This redirect also works in our Hyperview mobile app. -The Hyperview client’s ENTRY_POINT_URL points to http://0.0.0.0:5000/, and the server redirects it to http://0.0.0.0:5000/contacts. -But there’s no law that says we need to redirect to the same path in our web application and mobile app. -What if we used the Accept header to redirect to decide on the redirect path?

-
-
-
app.py
-
-
HTML_MIME = 'text/html'
+                        
+
+
+
    +
  1. +

    Redirect requests from "/" to "/contacts"

    +
  2. +
+
+
+

When someone requests to the root path of the web application, Flask redirects them to the + /contacts path. + This redirect also works in our Hyperview mobile app. + The Hyperview client’s ENTRY_POINT_URL points to + http://0.0.0.0:5000/, and the + server redirects it to + http://0.0.0.0:5000/contacts. + But there’s no law that says we need to redirect to the same path in our web + application and mobile app. + What if we used the Accept header to redirect to decide on the redirect path? +

+
+
+
app.py
+
+
HTML_MIME = 'text/html'
 HXML_MIME = 'application/vnd.hyperview+xml'
 
 @app.route("/")
@@ -16976,168 +23699,206 @@ def index():
       return redirect("/mobile/contacts") (2)
     else:
       return redirect("/web/contacts") (3)
-
-
-
-
    -
  1. -

    Determine whether the client wants HTML or HXML

    -
  2. -
  3. -

    If the client wants HXML, redirect them to /mobile/contacts

    -
  4. -
  5. -

    If the client wants HTML, redirect them to /web/contacts

    -
  6. -
-
-
-

The entrypoint is a fork in the road: if the client wants HTML, we redirect them to one path. -If the client wants HXML, we redirect them to a different path. -These redirects would be handled by different Flask views:

-
-
-
app.py
-
-
@app.route("/mobile/contacts")
+                        
+
+
+
    +
  1. +

    Determine whether the client wants HTML or HXML

    +
  2. +
  3. +

    If the client wants HXML, redirect them to /mobile/contacts

    +
  4. +
  5. +

    If the client wants HTML, redirect them to /web/contacts

    +
  6. +
+
+
+

The entrypoint is a fork in the road: if the client wants HTML, we redirect them to one path. + If the client wants HXML, we redirect them to a different path. + These redirects would be handled by different Flask views:

+
+
+
app.py
+
+
@app.route("/mobile/contacts")
 def mobile_contacts():
   # Render an HXML response
 
 @app.route("/web/contacts")
 def web_contacts():
   # Render an HTML response
-
-
-
-

The mobile_contacts() view would render an HXML template with a list of contacts. -Tapping a contact item would open a screen requested from /mobile/contacts/1, handled by a view mobile_contacts_view. -After the initial fork, all subsequent requests from our mobile app go to paths prefixed with /mobile/, and get handled by mobile-specific Flask views. -Likewise, all subsequent requests from the web app go to paths prefixed with /web/, and get handled by web-specific Flask views. -(Note that in practice, we would want to separate the web and mobile views into separate parts of our codebase: web_app.py and mobile_app.py. We may also choose not to prefix the web paths with /web/, if we want more elegant URLs displayed in the browser’s address bar.)

-
-
-

You may be thinking that the Redirect Fork leads to a lot of code duplication. -After all, we need to write double the number of views: one set for the web application, and one set for the mobile app. -That is true, which is why the Redirect Fork is only preferred if the two platforms require a disjointed set of view logic. -If the apps are similar on both platforms, Template Switching will save a lot of time and keep the apps consistent. -Even if we need to use the Redirect Fork, the bulk of the logic in our models can be shared by both sets of views. -In practice, you may start out using Template Switching, but then realize you need to implement a fork for platform-specific features. -In fact, we’re already doing that in the Contacts app. -When porting the app from web to mobile, we didn’t bring over certain features like archiving functionality. -The dynamic archive UI is a power feature that wouldn’t make sense on a mobile device. -Since our HXML templates don’t expose any entrypoints to the Archive functionality, we can treat it as “web-only” and not worry about supporting it in Hyperview.

-
-
-
-
-

13.8. Summary

-
-
    -
  • -

    Creating a Hyperview-powered mobile app is as simple as cloning a Git repo and configuring a single entrypoint URL.

    -
  • -
  • -

    Flask is perfectly suited for serving a Hyperview mobile app. The Jinja templating system can generate dynamic HXML based on the context from a view.

    -
  • -
  • -

    Using elements like <view>, <text>, <list>, and <item>, we can create native-feeling screens in HXML.

    -
  • -
  • -

    Using behaviors, we can implement interactions on the contacts list such as infinite scroll, search-as-you-type, and pull-to-refresh.

    -
  • -
  • -

    Events are a client-side feature of Hyperview that allows triggering behaviors across screens. They are useful to keep state in sync throughout the app, such as after editing or deleting a contact.

    -
  • -
  • -

    A Hyperview-powered mobile app can be bundled and released through the iOS and Android app stores. New screens and features can be added to the app just by updating the backend!

    -
  • -
  • -

    The same Flask backend can support both web and mobile apps. Using HTTP content negotiation, a server can render either HTML or HXML responses from the same view. More complex apps may require the use of redirects and platform-specific views.

    -
  • -
-
-
-
-
-
-

14. Extending the Hyperview Client

-
-
-

This chapter covers:

-
-
-
    -
  • -

    Adding phone call and email integration with a custom behavior action

    -
  • -
  • -

    Showing confirmation toast messages with a custom behavior action

    -
  • -
  • -

    Supporting a “swipeable row” UI with a custom Hyperview component

    -
  • -
-
-
-

In the previous chapter, we created a fully-featured native mobile version of our Contacts app. -Aside from customizing the entrypoint URL, we didn’t need to touch any code that runs on the mobile device. -We defined our mobile app’s UI and logic completely in the backend code, using Flask and HXML templates. -This is possible because the standard Hyperview client supports all of the basic features by mobile apps. -But the standard Hyperview client can’t do everything out of the box. -App developers want their apps to have unique touches like custom UIs or deep integration with platform capabilities. -To support these needs, the Hyperview client was designed to be extended with custom behavior actions and UI elements. -In this section, we will enhance our mobile app with examples of both.

-
-
-

Before diving in, a quick introduction on the tech stack we’ll be using. -The Hyperview client is written in React Native. -If you’re not familiar with React Native, it is a popular cross-platform framework for creating mobile apps. -It uses the same component-based API as React. -This means developers familiar with JavaScript and React can quickly pick up React Native. -React Native has a healthy ecosystem of open-source libraries. -We’ll be leveraging these libraries to create our custom extensions to the Hyperview client.

-
-
-

14.1. Adding Phone Calls and Email

-
-

Let’s start with the most obvious feature missing from our Contacts app: phone calls. -Mobile devices can make phone calls. -The contacts in our app have phone numbers. -Shouldn’t our app support calling those phone numbers? -And while we’re at it, our app should also support e-mailing the contacts.

-
-
-

On the web, calling phone numbers is supported with the tel: URI scheme, and e-mails are supported with the mailto: URI scheme:

-
-
-
tel and mailto schemes in HTML
-
-
<a href="tel:555-555-5555">Call</a> (1)
+                        
+
+
+

The mobile_contacts() view would render an HXML template with a list of + contacts. + Tapping a contact item would open a screen requested from /mobile/contacts/1, + handled by a view mobile_contacts_view. + After the initial fork, all subsequent requests from our mobile app go to paths prefixed + with /mobile/, and get handled by mobile-specific Flask views. + Likewise, all subsequent requests from the web app go to paths prefixed with + /web/, and get handled by web-specific Flask views. + (Note that in practice, we would want to separate the web and mobile views into separate + parts of our codebase: web_app.py and mobile_app.py. We may also + choose not to prefix the web paths with /web/, if we want more elegant URLs + displayed in the browser’s address bar.)

+
+
+

You may be thinking that the Redirect Fork leads to a lot of code duplication. + After all, we need to write double the number of views: one set for the web application, and + one set for the mobile app. + That is true, which is why the Redirect Fork is only preferred if the two platforms require + a disjointed set of view logic. + If the apps are similar on both platforms, Template Switching will save a lot of time and + keep the apps consistent. + Even if we need to use the Redirect Fork, the bulk of the logic in our models can be shared + by both sets of views. + In practice, you may start out using Template Switching, but then realize you need to + implement a fork for platform-specific features. + In fact, we’re already doing that in the Contacts app. + When porting the app from web to mobile, we didn’t bring over certain features like + archiving functionality. + The dynamic archive UI is a power feature that wouldn’t make sense on a mobile device. + Since our HXML templates don’t expose any entrypoints to the Archive functionality, we + can treat it as “web-only” and not worry about supporting it in Hyperview.

+
+
+
+
+

13.8. Summary

+
+
    +
  • +

    Creating a Hyperview-powered mobile app is as simple as cloning a Git repo and + configuring a single entrypoint URL.

    +
  • +
  • +

    Flask is perfectly suited for serving a Hyperview mobile app. The Jinja templating system + can generate dynamic HXML based on the context from a view.

    +
  • +
  • +

    Using elements like <view>, <text>, + <list>, and <item>, we can create native-feeling + screens in HXML.

    +
  • +
  • +

    Using behaviors, we can implement interactions on the contacts list such as infinite + scroll, search-as-you-type, and pull-to-refresh.

    +
  • +
  • +

    Events are a client-side feature of Hyperview that allows triggering behaviors across + screens. They are useful to keep state in sync throughout the app, such as after editing + or deleting a contact.

    +
  • +
  • +

    A Hyperview-powered mobile app can be bundled and released through the iOS and Android + app stores. New screens and features can be added to the app just by updating the + backend!

    +
  • +
  • +

    The same Flask backend can support both web and mobile apps. Using HTTP content + negotiation, a server can render either HTML or HXML responses from the same view. More + complex apps may require the use of redirects and platform-specific views.

    +
  • +
+
+
+
+
+
+

14. Extending the Hyperview Client

+
+
+

This chapter covers:

+
+
+
    +
  • +

    Adding phone call and email integration with a custom behavior action

    +
  • +
  • +

    Showing confirmation toast messages with a custom behavior action

    +
  • +
  • +

    Supporting a “swipeable row” UI with a custom Hyperview component

    +
  • +
+
+
+

In the previous chapter, we created a fully-featured native mobile version of our Contacts app. + Aside from customizing the entrypoint URL, we didn’t need to touch any code that runs on the + mobile device. + We defined our mobile app’s UI and logic completely in the backend code, using Flask and HXML + templates. + This is possible because the standard Hyperview client supports all of the basic features by mobile + apps. + But the standard Hyperview client can’t do everything out of the box. + App developers want their apps to have unique touches like custom UIs or deep integration with + platform capabilities. + To support these needs, the Hyperview client was designed to be extended with custom behavior + actions and UI elements. + In this section, we will enhance our mobile app with examples of both.

+
+
+

Before diving in, a quick introduction on the tech stack we’ll be using. + The Hyperview client is written in React Native. + If you’re not familiar with React Native, it is a popular cross-platform framework for + creating mobile apps. + It uses the same component-based API as React. + This means developers familiar with JavaScript and React can quickly pick up React Native. + React Native has a healthy ecosystem of open-source libraries. + We’ll be leveraging these libraries to create our custom extensions to the Hyperview client. +

+
+
+

14.1. Adding Phone Calls and Email

+
+

Let’s start with the most obvious feature missing from our Contacts app: phone calls. + Mobile devices can make phone calls. + The contacts in our app have phone numbers. + Shouldn’t our app support calling those phone numbers? + And while we’re at it, our app should also support e-mailing the contacts.

+
+
+

On the web, calling phone numbers is supported with the tel: URI scheme, and e-mails + are supported with the mailto: URI scheme:

+
+
+
tel and mailto schemes in HTML
+
+
<a href="tel:555-555-5555">Call</a> (1)
 <a href="mailto:joe@example.com">Email</a> (2)
-
-
-
-
    -
  1. -

    When clicked, prompt the user to call the given phone number

    -
  2. -
  3. -

    When clicked, open an e-mail client with the given address populated in the to: field.

    -
  4. -
-
-
-

The Hyperview client doesn’t support the tel: and mailto: URI schemes. -But we can add these capabilities to the client with custom behavior actions. -Remember that behaviors are interactions defined in HXML. -Behaviors have triggers (“press”, “refresh”) and actions (“update”, “share”). -The values of “action” are not limited to the set that comes in the Hyperview library. -So let’s define two new actions, “open-phone” and “open-email”.

-
-
-
Phone and Email actions
-
-
<view xmlns:comms="https://manning.com/hyperview/communications"> (1)
+                    
+
+
+
    +
  1. +

    When clicked, prompt the user to call the given phone number

    +
  2. +
  3. +

    When clicked, open an e-mail client with the given address populated in the + to: field.

    +
  4. +
+
+
+

The Hyperview client doesn’t support the tel: and mailto: URI + schemes. + But we can add these capabilities to the client with custom behavior actions. + Remember that behaviors are interactions defined in HXML. + Behaviors have triggers (“press”, “refresh”) and actions + (“update”, “share”). + The values of “action” are not limited to the set that comes in the Hyperview + library. + So let’s define two new actions, “open-phone” and “open-email”. +

+
+
+
Phone and Email actions
+
+
<view xmlns:comms="https://manning.com/hyperview/communications"> (1)
   <text>
     <behavior action="open-phone" comms:phone-number="555-555-5555" /> (2)
     Call
@@ -17147,60 +23908,68 @@ So let’s define two new actions, “open-phone” and “open-
     Email
   </text>
 </view>
-
-
-
-
    -
  1. -

    Define an alias for an XML namespace used by our new attributes

    -
  2. -
  3. -

    When pressed, prompt the user to call the given phone number

    -
  4. -
  5. -

    When pressed, open an e-mail client with the given address populated in the to: field.

    -
  6. -
-
-
-

Notice we defined the actual phone number and email address using separate attributes. -In HTML, the scheme and data are crammed into the href attribute. -HXML’s <behavior> elements give more options for representing the data. -I chose to use attributes, but we could’ve represented the phone number or email using child elements. -I’m also using a namespace to avoid potential future conflicts with other client extensions.

-
-
-

So far so good, but how does the Hyperview client know how to interpret open-phone and open-email, and how to reference the phone-number and email-address attributes? -This is where we finally need to write some JavaScript.

-
-
-

First, I’m going to add a 3rd-party library (react-native-communications) to our demo app. -This library provides a simple API that interacts with OS-level functionality for calls and emails.

-
-
-
-
> cd hyperview/demo
+                    
+
+
+
    +
  1. +

    Define an alias for an XML namespace used by our new attributes

    +
  2. +
  3. +

    When pressed, prompt the user to call the given phone number

    +
  4. +
  5. +

    When pressed, open an e-mail client with the given address populated in the + to: field.

    +
  6. +
+
+
+

Notice we defined the actual phone number and email address using separate attributes. + In HTML, the scheme and data are crammed into the href attribute. + HXML’s <behavior> elements give more options for representing the data. + I chose to use attributes, but we could’ve represented the phone number or email using + child elements. + I’m also using a namespace to avoid potential future conflicts with other client + extensions.

+
+
+

So far so good, but how does the Hyperview client know how to interpret open-phone + and open-email, and how to reference the phone-number and + email-address attributes? + This is where we finally need to write some JavaScript.

+
+
+

First, I’m going to add a 3rd-party library (react-native-communications) to + our demo app. + This library provides a simple API that interacts with OS-level functionality for calls and + emails.

+
+
+
+
> cd hyperview/demo
 > yarn add react-native-communications (1)
 > yarn start (2)
-
-
-
-
    -
  1. -

    Add dependency on react-native-communications

    -
  2. -
  3. -

    Re-start the mobile app

    -
  4. -
-
-
-

Next, we’ll create a new file, phone.js, that will implement the code associated with the open-phone action:

-
-
-
demo/src/phone.js
-
-
import { phonecall } from 'react-native-communications'; (1)
+                    
+
+
+
    +
  1. +

    Add dependency on react-native-communications

    +
  2. +
  3. +

    Re-start the mobile app

    +
  4. +
+
+
+

Next, we’ll create a new file, phone.js, that will implement the code + associated with the open-phone action:

+
+
+
demo/src/phone.js
+
+
import { phonecall } from 'react-native-communications'; (1)
 
 const namespace = "https://manning.com/hyperview/communications";
 
@@ -17213,46 +23982,54 @@ export default {
     }
   },
 };
-
-
-
-
    -
  1. -

    Import the function we need from the 3rd party library.

    -
  2. -
  3. -

    The name of the action

    -
  4. -
  5. -

    The callback that runs when the action triggers.

    -
  6. -
  7. -

    Get the phone number from the <behavior> element.

    -
  8. -
  9. -

    Pass the phone number to the function from the 3rd party library.

    -
  10. -
-
-
-

Custom actions are defined as a JavaScript object with two keys: action and callback. -This is how the Hyperview client associates a custom action in the HXML with our custom code. -The callback value is a function that takes a single parameter, behaviorElement. -This parameter is an XML DOM representation of the <behavior> element that triggered the action. -That means we can call methods on it like getAttribute, or access attributes like childNodes. -In this case, we use getAttributeNS to read the phone number from the phone-number attribute on the <behavior> element. -If the phone number is defined on the element, we can call the phonecall() function provided by the react-native-communications library.

-
-
-

There’s one more thing we need to do before we can use our custom action: register the action with the Hyperview client. -The Hyperview client is represented as a React Native component called Hyperview. -This component takes a prop called behaviors, which is an array of custom action objects like our “open-phone” action. -Let’s pass our “open-phone” implementation to the Hyperview component in our demo app.

-
-
-
demo/src/HyperviewScreen.js
-
-
import React, { PureComponent } from 'react';
+                    
+
+
+
    +
  1. +

    Import the function we need from the 3rd party library.

    +
  2. +
  3. +

    The name of the action

    +
  4. +
  5. +

    The callback that runs when the action triggers.

    +
  6. +
  7. +

    Get the phone number from the <behavior> element.

    +
  8. +
  9. +

    Pass the phone number to the function from the 3rd party library.

    +
  10. +
+
+
+

Custom actions are defined as a JavaScript object with two keys: action and + callback. + This is how the Hyperview client associates a custom action in the HXML with our custom code. + The callback value is a function that takes a single parameter, behaviorElement. + This parameter is an XML DOM representation of the <behavior> element that + triggered the action. + That means we can call methods on it like getAttribute, or access attributes like + childNodes. + In this case, we use getAttributeNS to read the phone number from the + phone-number attribute on the <behavior> element. + If the phone number is defined on the element, we can call the phonecall() function + provided by the react-native-communications library.

+
+
+

There’s one more thing we need to do before we can use our custom action: register the + action with the Hyperview client. + The Hyperview client is represented as a React Native component called Hyperview. + This component takes a prop called behaviors, which is an array of custom action + objects like our “open-phone” action. + Let’s pass our “open-phone” implementation to the Hyperview + component in our demo app.

+
+
+
demo/src/HyperviewScreen.js
+
+
import React, { PureComponent } from 'react';
 import Hyperview from 'hyperview';
 import OpenPhone from './phone'; (1)
 
@@ -17271,31 +24048,35 @@ export default class HyperviewScreen extends PureComponent {
     );
   }
 }
-
-
-
-
    -
  1. -

    Import the open-phone action

    -
  2. -
  3. -

    Create an array of custom actions

    -
  4. -
  5. -

    Pass the custom actions to the Hyperview component, as a prop called behaviors.

    -
  6. -
-
-
-

Under the hood, the Hyperview component is responsible for taking HXML and turning it into mobile UI elements. -It also handles triggering behavior actions based on user interactions. -By passing the “open-phone” action to Hyperview, we can now use it as a value for the action attribute on <behavior> elements. -In fact, let’s do that now by updating the show.xml template in our Flask app:

-
-
-
Snippet of hv/show.xml
-
-
{% block content %}
+                    
+
+
+
    +
  1. +

    Import the open-phone action

    +
  2. +
  3. +

    Create an array of custom actions

    +
  4. +
  5. +

    Pass the custom actions to the Hyperview component, as a prop called + behaviors.

    +
  6. +
+
+
+

Under the hood, the Hyperview component is responsible for taking HXML and turning + it into mobile UI elements. + It also handles triggering behavior actions based on user interactions. + By passing the “open-phone” action to Hyperview, we can now use it as a value for + the action attribute on <behavior> elements. + In fact, let’s do that now by updating the show.xml template in our Flask + app:

+
+
+
Snippet of hv/show.xml
+
+
{% block content %}
 <view style="details">
   <text style="contact-name">{{ contact.first }} {{ contact.last }}</text>
 
@@ -17322,96 +24103,113 @@ In fact, let’s do that now by updating the show.xml template
   </view>
 </view>
 {% endblock %}
-
-
-
-
    -
  1. -

    Add a behavior to the phone number section that triggers on “press”.

    -
  2. -
  3. -

    Trigger the new “open-phone” action.

    -
  4. -
  5. -

    Set the attribute expected by the “open-phone” action.

    -
  6. -
  7. -

    Same idea, with a different action (“open-email”)

    -
  8. -
-
-
-

I skipped over the implementation of the second custom action, “open-email”. -As you can guess, this action will open a system-level email composer to let the user send an email to their contact. -The implementation of “open-email” is almost identical to “open-phone”. -The react-native-communications library exposes a function called email(), so we just wrap it and pass arguments to it in the same way.

-
-
-

There we have a complete example of extending the client with some custom behavior actions! -We chose a new name for our actions (“open-phone” and “open-email”), and mapped those names to functions. -The functions take <behavior> elements and can run any arbitrary React Native code. -We wrapped an existing 3rd party library, and read attributes set on the <behavior> element to pass data to the library. -After re-starting our demo app, our client has new capabilities we can immediately utilize by referencing the actions from our HXML templates.

-
-
-
-

14.2. Adding Toast Messages

-
-

The phone and email actions added in the previous section are examples of “system actions”. -System actions trigger some UI or capability provided by the device’s OS. -But custom actions are not limited to only interacting with OS-level APIs. -Remember, the callbacks that implement actions can run arbitrary code, including code that renders our own UI elements. -This next custom action example will do just that: render a custom confirmation toast UI element.

-
-
-

If you recall, our Contacts web app shows messages upon successful actions, such as deleting or creating a contact. -These messages are generated in the Flask backend using the flash() function, called from the views. -Then the base layout.html template rendered the messages into the final web page.

-
-
-
Snippet templates/layout.html
-
-
{% for message in get_flashed_messages() %}
+                    
+
+
+
    +
  1. +

    Add a behavior to the phone number section that triggers on “press”.

    +
  2. +
  3. +

    Trigger the new “open-phone” action.

    +
  4. +
  5. +

    Set the attribute expected by the “open-phone” action.

    +
  6. +
  7. +

    Same idea, with a different action (“open-email”)

    +
  8. +
+
+
+

I skipped over the implementation of the second custom action, “open-email”. + As you can guess, this action will open a system-level email composer to let the user send an + email to their contact. + The implementation of “open-email” is almost identical to “open-phone”. + The react-native-communications library exposes a function called + email(), so we just wrap it and pass arguments to it in the same way.

+
+
+

There we have a complete example of extending the client with some custom behavior actions! + We chose a new name for our actions (“open-phone” and “open-email”), and + mapped those names to functions. + The functions take <behavior> elements and can run any arbitrary React Native + code. + We wrapped an existing 3rd party library, and read attributes set on the + <behavior> element to pass data to the library. + After re-starting our demo app, our client has new capabilities we can immediately utilize by + referencing the actions from our HXML templates.

+
+
+
+

14.2. Adding Toast Messages

+
+

The phone and email actions added in the previous section are examples of “system + actions”. + System actions trigger some UI or capability provided by the device’s OS. + But custom actions are not limited to only interacting with OS-level APIs. + Remember, the callbacks that implement actions can run arbitrary code, including code that + renders our own UI elements. + This next custom action example will do just that: render a custom confirmation toast UI + element.

+
+
+

If you recall, our Contacts web app shows messages upon successful actions, such as deleting or + creating a contact. + These messages are generated in the Flask backend using the flash() function, + called from the views. + Then the base layout.html template rendered the messages into the final web page. +

+
+
+
Snippet templates/layout.html
+
+
{% for message in get_flashed_messages() %}
   <div class="flash">{{ message }}</div>
 {% endfor %}
-
-
-
-

Our Flask app still includes the calls to flash(), but the Hyperview app is not accessing the flashed message to display to the user. -Let’s add that support now. -We could just show the messages using a similar technique to the web app: loop through the messages and render some <text> elements in layout.xml. -This approach has a major downside: the rendered messages would be tied to a specific screen. -If that screen was hidden by a navigation action, the message would be hidden too. -What we really want is for our toast UI to display “above” all of the screens in the navigation stack. -That way, the toast would remain visible (fading away after a few seconds), even if the stack of screens changes below. -To display some UI outside of the <screen> elements, we’re going to need to extend the Hyperview client with a new custom action, show-toast. -This is another opportunity to use an open-source library, react-native-root-toast. -Let’s add this library to our demo app.

-
-
-
-
> cd hyperview/demo
+                    
+
+
+

Our Flask app still includes the calls to flash(), but the Hyperview app is not + accessing the flashed message to display to the user. + Let’s add that support now. + We could just show the messages using a similar technique to the web app: loop through the + messages and render some <text> elements in layout.xml. + This approach has a major downside: the rendered messages would be tied to a specific screen. + If that screen was hidden by a navigation action, the message would be hidden too. + What we really want is for our toast UI to display “above” all of the screens in the + navigation stack. + That way, the toast would remain visible (fading away after a few seconds), even if the stack of + screens changes below. + To display some UI outside of the <screen> elements, we’re going to + need to extend the Hyperview client with a new custom action, show-toast. + This is another opportunity to use an open-source library, react-native-root-toast. + Let’s add this library to our demo app.

+
+
+
+
> cd hyperview/demo
 > yarn add react-native-root-toast (1)
 > yarn start (2)
-
-
-
-
    -
  1. -

    Add dependency on react-native-root-toast

    -
  2. -
  3. -

    Re-start the mobile app

    -
  4. -
-
-
-

Now, we can write the code to implement the toast UI as a custom action.

-
-
-
demo/src/toast.js
-
-
import Toast from 'react-native-root-toast'; (1)
+                    
+
+
+
    +
  1. +

    Add dependency on react-native-root-toast

    +
  2. +
  3. +

    Re-start the mobile app

    +
  4. +
+
+
+

Now, we can write the code to implement the toast UI as a custom action.

+
+
+
demo/src/toast.js
+
+
import Toast from 'react-native-root-toast'; (1)
 
 const namespace = "https://manning.com/hyperview/toast";
 
@@ -17424,39 +24222,44 @@ export default {
     }
   },
 };
-
-
-
-
    -
  1. -

    Import the Toast API.

    -
  2. -
  3. -

    The name of the action

    -
  4. -
  5. -

    The callback that runs when the action triggers

    -
  6. -
  7. -

    Pass the message to the toast library

    -
  8. -
-
-
-

This code looks very similar to the implementation of open-phone. -Both callbacks follow a similar pattern: read namespaced attributes from the <behavior> element, and pass those values to a 3rd party library. -For simplicity, I’m hard-coding options to show the toast at the top of the screen, fading out after 2 seconds. -But react-native-root-toast exposes many options for positioning, timing of animations, colors, and more. -We could specify these options using extra attributes on behaviorElement to make the action more configurable. -For our purposes, we will just stick to a bare-bones implementation.

-
-
-

Now we just need to register our custom action with the <Hyperview> component, by passing it to the behaviors prop.

-
-
-
demo/src/HyperviewScreen.js
-
-
import React, { PureComponent } from 'react';
+                    
+
+
+
    +
  1. +

    Import the Toast API.

    +
  2. +
  3. +

    The name of the action

    +
  4. +
  5. +

    The callback that runs when the action triggers

    +
  6. +
  7. +

    Pass the message to the toast library

    +
  8. +
+
+
+

This code looks very similar to the implementation of open-phone. + Both callbacks follow a similar pattern: read namespaced attributes from the + <behavior> element, and pass those values to a 3rd party library. + For simplicity, I’m hard-coding options to show the toast at the top of the screen, fading + out after 2 seconds. + But react-native-root-toast exposes many options for positioning, timing of + animations, colors, and more. + We could specify these options using extra attributes on behaviorElement to make + the action more configurable. + For our purposes, we will just stick to a bare-bones implementation.

+
+
+

Now we just need to register our custom action with the <Hyperview> component, + by passing it to the behaviors prop.

+
+
+
demo/src/HyperviewScreen.js
+
+
import React, { PureComponent } from 'react';
 import Hyperview from 'hyperview';
 import OpenEmail from './email';
 import OpenPhone from './phone';
@@ -17469,46 +24272,52 @@ export default class HyperviewScreen extends PureComponent {
 
   // ... omitted for brevity
 }
-
-
-
-
    -
  1. -

    Import the show-toast action

    -
  2. -
  3. -

    Pass the action to the Hyperview component, as a prop called behaviors.

    -
  4. -
-
-
-

All that’s left to do is trigger the show-toast action from our HXML. -There are three user actions that result in showing a toast message:

-
-
-
    -
  1. -

    Creating a new contact

    -
  2. -
  3. -

    Updating an existing contact

    -
  4. -
  5. -

    Deleting a contact

    -
  6. -
-
-
-

The first two actions are implemented in our app using the same HXML template, form_fields.xml. -Upon successfully creating or updating a contact, this template will reload the screen and trigger an event, using behaviors that trigger on “load”. -The deletion action also uses behaviors that trigger on “load”, defined in the deleted.xml template. -So both form_fields.xml and deleted.xml need to be modified to also show toasts on load. -Since the actual behaviors will be the same in both templates, let’s create a shared template to reuse the HXML.

-
-
-
hv/templates/toasts.xml
-
-
{% for message in get_flashed_messages() %}
+                    
+
+
+
    +
  1. +

    Import the show-toast action

    +
  2. +
  3. +

    Pass the action to the Hyperview component, as a prop called + behaviors.

    +
  4. +
+
+
+

All that’s left to do is trigger the show-toast action from our HXML. + There are three user actions that result in showing a toast message:

+
+
+
    +
  1. +

    Creating a new contact

    +
  2. +
  3. +

    Updating an existing contact

    +
  4. +
  5. +

    Deleting a contact

    +
  6. +
+
+
+

The first two actions are implemented in our app using the same HXML template, + form_fields.xml. + Upon successfully creating or updating a contact, this template will reload the screen and + trigger an event, using behaviors that trigger on “load”. + The deletion action also uses behaviors that trigger on “load”, defined in the + deleted.xml template. + So both form_fields.xml and deleted.xml need to be modified to also + show toasts on load. + Since the actual behaviors will be the same in both templates, let’s create a shared + template to reuse the HXML.

+
+
+
hv/templates/toasts.xml
+
+
{% for message in get_flashed_messages() %}
   <behavior (1)
     xmlns:toast="https://manning.com/hyperview/toast"
     trigger="load" (2)
@@ -17516,34 +24325,35 @@ Since the actual behaviors will be the same in both templates, let’s creat
     toast:message="{{ message }}" (4)
   />
 {% endfor %}
-
-
-
-
    -
  1. -

    Define a behavior for each message to display

    -
  2. -
  3. -

    Trigger this behavior as soon as the screen loads

    -
  4. -
  5. -

    Trigger the new “show-toast” action.

    -
  6. -
  7. -

    The “show-toast” action will display the flashed message in its UI.

    -
  8. -
-
-
-

Like in layout.html of the web app, we loop through all of the flashed messages and render some markup for each message. -However, in the web app, the message was directly rendered into the web page. -In the Hyperview app, each message is displayed using a behavior that triggers our custom UI. -Now we just need to include this template in form_fields.xml:

-
-
-
Snippet of hv/templates/form_fields.xml
-
-
<view xmlns="https://hyperview.org/hyperview" style="edit-group">
+                    
+
+
+
    +
  1. +

    Define a behavior for each message to display

    +
  2. +
  3. +

    Trigger this behavior as soon as the screen loads

    +
  4. +
  5. +

    Trigger the new “show-toast” action.

    +
  6. +
  7. +

    The “show-toast” action will display the flashed message in its UI.

    +
  8. +
+
+
+

Like in layout.html of the web app, we loop through all of the flashed messages and + render some markup for each message. + However, in the web app, the message was directly rendered into the web page. + In the Hyperview app, each message is displayed using a behavior that triggers our custom UI. + Now we just need to include this template in form_fields.xml:

+
+
+
Snippet of hv/templates/form_fields.xml
+
+
<view xmlns="https://hyperview.org/hyperview" style="edit-group">
   {% if saved %}
     {% include "hv/toasts.xml" %} (1)
     <behavior trigger="load" once="true" action="dispatch-event" event-name="contact-updated" />
@@ -17551,107 +24361,123 @@ Now we just need to include this template in form_fields.xml:

{% endif %} <!-- omitted for brevity --> </view>
-
-
-
-
    -
  1. -

    Show the toasts as soon as the screen loads.

    -
  2. -
-
-
-

And we can do the same thing in deleted.xml:

-
-
-
hv/templates/deleted.xml
-
-
<view xmlns="https://hyperview.org/hyperview">
+                    
+
+
+
    +
  1. +

    Show the toasts as soon as the screen loads.

    +
  2. +
+
+
+

And we can do the same thing in deleted.xml:

+
+
+
hv/templates/deleted.xml
+
+
<view xmlns="https://hyperview.org/hyperview">
   {% include "hv/toasts.xml" %} (1)
   <behavior trigger="load" action="dispatch-event" event-name="contact-updated" />
   <behavior trigger="load" action="back" />
 </view>
-
-
-
-
    -
  1. -

    Show the toasts as soon as the screen loads.

    -
  2. -
-
-
-

In both form_fields.xml and deleted.xml, multiple behaviors get triggered on “load”. -In deleted.xml, we immediately navigate back to the previous screen. -In form_fields.xml, we immediately reload the current screen to show the Contact details. -If we rendered our toast UI elements directly in the screen, the user would barely see them before the screen disappeared or reloaded. -By using a custom action, the toast UI remains visible even while the screens change beneath them.

-
-
-
-screenshot hyperview toast -
-
Figure 18. Toast shown during back navigation
-
-
-
-

14.3. Swipe gesture on Contacts

-
-

To add communication capabilities and the toast UI, we extended the client with custom behavior actions. -But the Hyperview client can also be extended with custom UI components that render on the screen. -Custom components are implemented as React Native components. -That means anything that’s possible in React Native can be done in Hyperview as well! -Custom components open up endless possibilities to build rich mobile apps with the Hypermedia architecture.

-
-
-

To illustrate the possibilities, we will extend the Hyperview client in our mobile app to add a “swipeable row” component. -How does it work? -The “swipeable row” component supports a horizontal swiping gesture. -As the user swipes this component from right to left, the component will slide over, revealing a series of action buttons. -Each action button will be able to trigger standard Hyperview behaviors when pressed. -We will use this custom component in our Contacts List screen. -Each contact item will be a “swipeable row”, and the actions will give quick access to edit and delete actions for the contact.

-
-
-
-screenshot hyperview swipe -
-
Figure 19. Swipeable contact item
-
-
-

Designing The Component

-
-

Rather than implementing the swipe gesture from scratch, we will once again use an open-source third-party library: react-native-swipeable.

-
-
-
-
> cd hyperview/demo
+                    
+
+
+
    +
  1. +

    Show the toasts as soon as the screen loads.

    +
  2. +
+
+
+

In both form_fields.xml and deleted.xml, multiple behaviors get + triggered on “load”. + In deleted.xml, we immediately navigate back to the previous screen. + In form_fields.xml, we immediately reload the current screen to show the Contact + details. + If we rendered our toast UI elements directly in the screen, the user would barely see them + before the screen disappeared or reloaded. + By using a custom action, the toast UI remains visible even while the screens change beneath + them.

+
+
+
+ screenshot hyperview toast +
+
Figure 18. Toast shown during back navigation
+
+
+
+

14.3. Swipe gesture on Contacts

+
+

To add communication capabilities and the toast UI, we extended the client with custom behavior + actions. + But the Hyperview client can also be extended with custom UI components that render on the + screen. + Custom components are implemented as React Native components. + That means anything that’s possible in React Native can be done in Hyperview as well! + Custom components open up endless possibilities to build rich mobile apps with the Hypermedia + architecture.

+
+
+

To illustrate the possibilities, we will extend the Hyperview client in our mobile app to add a + “swipeable row” component. + How does it work? + The “swipeable row” component supports a horizontal swiping gesture. + As the user swipes this component from right to left, the component will slide over, revealing a + series of action buttons. + Each action button will be able to trigger standard Hyperview behaviors when pressed. + We will use this custom component in our Contacts List screen. + Each contact item will be a “swipeable row”, and the actions will give quick access + to edit and delete actions for the contact.

+
+
+
+ screenshot hyperview swipe +
+
Figure 19. Swipeable contact item
+
+
+

Designing The Component

+
+

Rather than implementing the swipe gesture from scratch, we will once again use an + open-source third-party library: react-native-swipeable.

+
+
+
+
> cd hyperview/demo
 > yarn add react-native-swipeable (1)
 > yarn start (2)
-
-
-
-
    -
  1. -

    Add dependency on react-native-swipeable

    -
  2. -
  3. -

    Re-start the mobile app

    -
  4. -
-
-
-

This library provides a React Native component called Swipeable. -It can render any React Native components as its main content (the part that can be swiped). -It also takes an array of React Native components as a prop to render as the action buttons. -When designing a custom component, we like to define the HXML of the component before writing the code. -This way, we can make sure the markup is expressive but succinct, and will work with the underlying library. -For the swipeable row, we need a way to represent the entire component, the main content, and one of many buttons. -I came up with something that looks like this:

-
-
-
-
<swipe:row xmlns:swipe="https://manning.com/hyperview/swipeable"> (1)
+                        
+
+
+
    +
  1. +

    Add dependency on react-native-swipeable

    +
  2. +
  3. +

    Re-start the mobile app

    +
  4. +
+
+
+

This library provides a React Native component called Swipeable. + It can render any React Native components as its main content (the part that can be swiped). + It also takes an array of React Native components as a prop to render as the action buttons. + When designing a custom component, we like to define the HXML of the component before + writing the code. + This way, we can make sure the markup is expressive but succinct, and will work with the + underlying library. + For the swipeable row, we need a way to represent the entire component, the main content, + and one of many buttons. + I came up with something that looks like this:

+
+
+
+
<swipe:row xmlns:swipe="https://manning.com/hyperview/swipeable"> (1)
   <swipe:main> (2)
     <!-- main content shown here -->
   </swipe:main>
@@ -17664,72 +24490,82 @@ I came up with something that looks like this:

<!-- second button that appears when swiping --> </swipe:button> </swipe:row>
-
-
-
-
    -
  1. -

    Parent element encapsulating the entire swipeable row, with custom namespace

    -
  2. -
  3. -

    The main content of the swipeable row, can hold any HXML

    -
  4. -
  5. -

    The first button that appears when swiping, can hold any HXML

    -
  6. -
  7. -

    The second button that appears when swiping, can hold any HXML

    -
  8. -
-
-
-

This structure clearly separates the main content from the buttons. -It also supports one, two, or more buttons. -Buttons appear in the order of definition, making it easy to swap the order. -This design covers everything we need to implement a swipeable row for our contacts list. -But it’s also generic enough to be reusable. -The markup above contains nothing specific to the contact name, editing the contact, or deleting the contact. -If down the line, we add another list screen to our app, we can use this component to make the items in that list swipeable.

-
-
-
-

Implementing The Component

-
-

Now that we know the HXML structure of our custom component, we can write the code to implement it. -What does that code look like? -Hyperview components are written as React Native components. -These React Native components are mapped to a unique XML namespace and tag name. -When the Hyperview client encounters that namespace and tag name in the HXML, it delegates rendering of the HXML element to the matching React Native component. -As part of delegation, the Hyperview Client passes several props to the React Native component:

-
-
-
    -
  • -

    element: The XML DOM element that maps to the React Native component

    -
  • -
  • -

    stylesheets: The styles defined in the <screen>

    -
  • -
  • -

    onUpdate: The function to call when the component triggers a behavior

    -
  • -
  • -

    option: Miscellaneous settings used by the Hyperview client.

    -
  • -
-
-
-

Our swipeable row component is a container with slots to render arbitrary main content and buttons. -That means it needs to delegate rendering back to the Hyperview client to render those parts of the UI. -This is done with a public function exposed by the Hyperview client, Hyperview.renderChildren().

-
-
-

Now that we know how custom Hyperview components are implemented, let’s write the code for our swipeable row.

-
-
-
demo/src/swipeable.js
-
-
import React, { PureComponent } from 'react';
+                        
+
+
+
    +
  1. +

    Parent element encapsulating the entire swipeable row, with custom namespace

    +
  2. +
  3. +

    The main content of the swipeable row, can hold any HXML

    +
  4. +
  5. +

    The first button that appears when swiping, can hold any HXML

    +
  6. +
  7. +

    The second button that appears when swiping, can hold any HXML

    +
  8. +
+
+
+

This structure clearly separates the main content from the buttons. + It also supports one, two, or more buttons. + Buttons appear in the order of definition, making it easy to swap the order. + This design covers everything we need to implement a swipeable row for our contacts list. + But it’s also generic enough to be reusable. + The markup above contains nothing specific to the contact name, editing the contact, or + deleting the contact. + If down the line, we add another list screen to our app, we can use this component to make + the items in that list swipeable.

+
+
+
+

Implementing The Component

+
+

Now that we know the HXML structure of our custom component, we can write the code to + implement it. + What does that code look like? + Hyperview components are written as React Native components. + These React Native components are mapped to a unique XML namespace and tag name. + When the Hyperview client encounters that namespace and tag name in the HXML, it delegates + rendering of the HXML element to the matching React Native component. + As part of delegation, the Hyperview Client passes several props to the React Native + component:

+
+
+
    +
  • +

    element: The XML DOM element that maps to the React Native component

    +
  • +
  • +

    stylesheets: The styles defined in the <screen>

    +
  • +
  • +

    onUpdate: The function to call when the component triggers a behavior +

    +
  • +
  • +

    option: Miscellaneous settings used by the Hyperview client.

    +
  • +
+
+
+

Our swipeable row component is a container with slots to render arbitrary main content and + buttons. + That means it needs to delegate rendering back to the Hyperview client to render those parts + of the UI. + This is done with a public function exposed by the Hyperview client, + Hyperview.renderChildren().

+
+
+

Now that we know how custom Hyperview components are implemented, let’s write the code + for our swipeable row.

+
+
+
demo/src/swipeable.js
+
+
import React, { PureComponent } from 'react';
 import Hyperview from 'hyperview';
 import Swipeable from 'react-native-swipeable';
 
@@ -17762,83 +24598,102 @@ export default class SwipeableRow extends PureComponent { (1)
-
-
-
-
    -
  1. -

    Class-based React Native component

    -
  2. -
  3. -

    Map this component to the given HXML namespace

    -
  4. -
  5. -

    Map this component to the given HXML tag name

    -
  6. -
  7. -

    Function that returns an array of React Native components for each <button> element.

    -
  8. -
  9. -

    Delegate to the Hyperview client to render each button

    -
  10. -
  11. -

    Pass the buttons and main content to the third-party library

    -
  12. -
  13. -

    Delegate to the Hyperview client to render the main content

    -
  14. -
-
-
-

The SwipeableRow class implements a React Native component. -At the top of the class, we set a static namespaceURI property and localName property. -These properties map the React Native component to a unique namespace and tag name pair in the HXML. -This is how the Hyperview client knows to delegate to SwipeableRow when encountering custom elements in the HXML. -At the bottom of the class, you’ll see a render() method. -render() gets called by React Native to return the rendered component. -Since React Native is built on principle of composition, render() typically returns a composition of other React Native components. -In this case, we return the Swipeable component (provided by the react-native-swipeable library), composed with React Native components for the buttons and main content. -The React Native components for the buttons and main content are created using a similar process:

-
-
-
    -
  • -

    Find the specific child elements (<button> or <main>)

    -
  • -
  • -

    Turn those elements into React Native components using Hyperview.renderChildren()

    -
  • -
  • -

    Set the components as children or props of Swipeable.

    -
  • -
-
-
-
-figure hyperview components -
-
Figure 20. Component rendering delegation
-
-
-

I realize this code may be hard to follow if you’ve never worked with React or React Native. -That’s ok. -The important takeaway is: we can write code to translate arbitrary HXML into React Native components. -The structure of the HXML (both attributes and elements) can be used to represent multiple facets of the UI (in this case, the buttons and main content). -Finally, the code can delegate rendering of child components back to the Hyperview client. -That means this swipeable row component is completely generic. -The actual structure and styling and interactions of the main content and buttons can be defined in the HXML. -Creating a generic component means we can reuse it across multiple screens for different purposes. -If we add more custom components or new behavior actions in the future, they will work with our swipeable row implementation.

-
-
-

The last thing to do is register this new component with the Hyperview client. -The process is similar to registering custom actions. -Custom components are passed as a separate components prop to the Hyperview component.

-
-
-
demo/src/HyperviewScreen.js
-
-
import React, { PureComponent } from 'react';
+                        
+
+
+
    +
  1. +

    Class-based React Native component

    +
  2. +
  3. +

    Map this component to the given HXML namespace

    +
  4. +
  5. +

    Map this component to the given HXML tag name

    +
  6. +
  7. +

    Function that returns an array of React Native components for each + <button> element.

    +
  8. +
  9. +

    Delegate to the Hyperview client to render each button

    +
  10. +
  11. +

    Pass the buttons and main content to the third-party library

    +
  12. +
  13. +

    Delegate to the Hyperview client to render the main content

    +
  14. +
+
+
+

The SwipeableRow class implements a React Native component. + At the top of the class, we set a static namespaceURI property and + localName property. + These properties map the React Native component to a unique namespace and tag name pair in + the HXML. + This is how the Hyperview client knows to delegate to SwipeableRow when + encountering custom elements in the HXML. + At the bottom of the class, you’ll see a render() method. + render() gets called by React Native to return the rendered component. + Since React Native is built on principle of composition, render() typically + returns a composition of other React Native components. + In this case, we return the Swipeable component (provided by the + react-native-swipeable library), composed with React Native components for the + buttons and main content. + The React Native components for the buttons and main content are created using a similar + process: +

+
+
+
    +
  • +

    Find the specific child elements (<button> or + <main>)

    +
  • +
  • +

    Turn those elements into React Native components using + Hyperview.renderChildren()

    +
  • +
  • +

    Set the components as children or props of Swipeable.

    +
  • +
+
+
+
+ figure hyperview components +
+
Figure 20. Component rendering delegation
+
+
+

I realize this code may be hard to follow if you’ve never worked with React or React + Native. + That’s ok. + The important takeaway is: we can write code to translate arbitrary HXML into React Native + components. + The structure of the HXML (both attributes and elements) can be used to represent multiple + facets of the UI (in this case, the buttons and main content). + Finally, the code can delegate rendering of child components back to the Hyperview client. + That means this swipeable row component is completely generic. + The actual structure and styling and interactions of the main content and buttons can be + defined in the HXML. + Creating a generic component means we can reuse it across multiple screens for different + purposes. + If we add more custom components or new behavior actions in the future, they will work with + our swipeable row implementation.

+
+
+

The last thing to do is register this new component with the Hyperview client. + The process is similar to registering custom actions. + Custom components are passed as a separate components prop to the + Hyperview component.

+
+
+
demo/src/HyperviewScreen.js
+
+
import React, { PureComponent } from 'react';
 import Hyperview from 'hyperview';
 import OpenEmail from './email';
 import OpenPhone from './phone';
@@ -17862,48 +24717,52 @@ export default class HyperviewScreen extends PureComponent {
     );
   }
 }
-
-
-
-
    -
  1. -

    Import the SwipeableRow component

    -
  2. -
  3. -

    Create an array of custom components

    -
  4. -
  5. -

    Pass the custom component to the Hyperview component, as a prop called components.

    -
  6. -
-
-
-

We’re now ready to update our HXML templates to make use of the new swipeable row component!

-
-
-
Using The Component
-
-

Currently, the HXML for a contact item in the list consists of a <behavior> and <text> element:

-
-
-
Snippet of hv/rows.xml
-
-
<item key="{{ contact.id }}" style="contact-item">
+                        
+
+
+
    +
  1. +

    Import the SwipeableRow component

    +
  2. +
  3. +

    Create an array of custom components

    +
  4. +
  5. +

    Pass the custom component to the Hyperview component, as a prop called + components.

    +
  6. +
+
+
+

We’re now ready to update our HXML templates to make use of the new swipeable row + component!

+
+
+
Using The Component
+
+

Currently, the HXML for a contact item in the list consists of a + <behavior> and <text> element:

+
+
+
Snippet of hv/rows.xml
+
+
<item key="{{ contact.id }}" style="contact-item">
   <behavior trigger="press" action="push" href="/contacts/{{ contact.id }}" /> (1)
   <text style="contact-item-label">
     <!-- omitted for brevity -->
   </text>
 </item>
-
-
-
-

With our swipeable row component, this markup will become the “main” UI. -So let’s start by adding <row> and <main> as parent elements.

-
-
-
Adding swipeable row hv/rows.xml
-
-
<item key="{{ contact.id }}">
+                            
+
+
+

With our swipeable row component, this markup will become the “main” UI. + So let’s start by adding <row> and <main> as + parent elements.

+
+
+
Adding swipeable row hv/rows.xml
+
+
<item key="{{ contact.id }}">
   <swipe:row xmlns:swipe="https://manning.com/hyperview/swipeable"> (1)
     <swipe:main> (2)
       <view style="contact-item"> (3)
@@ -17915,35 +24774,41 @@ So let’s start by adding <row> and <main>
-
-
-
-
    -
  1. -

    Added <swipe:row> parent element, with namespace alias for swipe.

    -
  2. -
  3. -

    Added <swipe:main> element to define the main content

    -
  4. -
  5. -

    Wrapped the existing <behavior> and <text> elements in a <view>

    -
  6. -
-
-
-

Previously, the contact-item style was set on the <item> element. -That made sense when the <item> element was the container for the main content of the list item. -Now that the main content is a child of <swipe:main>, we need to introduce a new <view> where we apply the styles.

-
-
-

If we reload our backend and mobile app, you won’t experience any changes on the Contacts List screen yet. -Without any action buttons defined, there’s nothing to reveal when swiping a row. -Let’s add two buttons to the swipeable row.

-
-
-
Adding swipeable row hv/rows.xml
-
-
<item key="{{ contact.id }}">
+                            
+
+
+
    +
  1. +

    Added <swipe:row> parent element, with namespace alias for + swipe.

    +
  2. +
  3. +

    Added <swipe:main> element to define the main content

    +
  4. +
  5. +

    Wrapped the existing <behavior> and <text> + elements in a <view>

    +
  6. +
+
+
+

Previously, the contact-item style was set on the <item> + element. + That made sense when the <item> element was the container for the + main content of the list item. + Now that the main content is a child of <swipe:main>, we need to + introduce a new <view> where we apply the styles.

+
+
+

If we reload our backend and mobile app, you won’t experience any changes on the + Contacts List screen yet. + Without any action buttons defined, there’s nothing to reveal when swiping a row. + Let’s add two buttons to the swipeable row.

+
+
+
Adding swipeable row hv/rows.xml
+
+
<item key="{{ contact.id }}">
   <swipe:row xmlns:swipe="https://manning.com/hyperview/swipeable"> (1)
     <swipe:main>
       <!-- omitted for brevity -->
@@ -17962,55 +24827,62 @@ Let’s add two buttons to the swipeable row.

</swipe:button> </swipe:row> </item>
-
-
-
-
    -
  1. -

    Added <swipe:button> for edit action

    -
  2. -
  3. -

    Added <swipe:button> for delete action

    -
  4. -
-
-
-

Now if we use our mobile app, we can see the swipeable row in action! -As you swipe the contact item, the “Edit” and “Delete” buttons reveal themselves. -But they don’t do anything yet. We need to add some behaviors to these buttons. -The “Edit” button is straight-forward: pressing it should open the contact details screen in edit mode.

-
-
-
Snippet of hv/rows.xml
-
-
<swipe:button>
+                            
+
+
+
    +
  1. +

    Added <swipe:button> for edit action

    +
  2. +
  3. +

    Added <swipe:button> for delete action

    +
  4. +
+
+
+

Now if we use our mobile app, we can see the swipeable row in action! + As you swipe the contact item, the “Edit” and “Delete” buttons + reveal themselves. + But they don’t do anything yet. We need to add some behaviors to these buttons. + The “Edit” button is straight-forward: pressing it should open the contact + details screen in edit mode.

+
+
+
Snippet of hv/rows.xml
+
+
<swipe:button>
   <view style="swipe-button">
     <behavior trigger="press" action="push" href="/contacts/{{ contact.id }}/edit" /> (1)
     <text style="button-label">Edit</text>
   </view>
 </swipe:button>
-
-
-
-
    -
  1. -

    When pressed, push a new screen with the Edit Contact UI.

    -
  2. -
-
-
-

The “Delete” button is a bit more complicated. -There’s no screen to open for deletion, so what should happen when the user presses this button? -Perhaps we use the same interaction as the “Delete” button on the Edit Contact screen. -That interaction brings up a system dialog, asking the user to confirm the deletion. -If the user confirms, the Hyperview client makes a POST request to /contacts/<contact_id>/delete, and appends the response to the screen. -The response triggers a few behaviors immediately to reload the contacts list and show a toast message. -This interaction will work for our action button as well:

-
-
-
Snippet of hv/rows.xml
-
-
<swipe:button>
+                            
+
+
+
    +
  1. +

    When pressed, push a new screen with the Edit Contact UI.

    +
  2. +
+
+
+

The “Delete” button is a bit more complicated. + There’s no screen to open for deletion, so what should happen when the user + presses this button? + Perhaps we use the same interaction as the “Delete” button on the Edit + Contact screen. + That interaction brings up a system dialog, asking the user to confirm the deletion. + If the user confirms, the Hyperview client makes a POST request to + /contacts/<contact_id>/delete, and appends the response to the + screen. + The response triggers a few behaviors immediately to reload the contacts list and show a + toast message. + This interaction will work for our action button as well:

+
+
+
Snippet of hv/rows.xml
+
+
<swipe:button>
   <view style="swipe-button">
     <behavior (1)
       xmlns:alert="https://hyperview.org/hyperview-alert"
@@ -18033,177 +24905,232 @@ This interaction will work for our action button as well:

<text style="button-label-delete">Delete</text> </view> </swipe:button>
-
-
-
-
    -
  1. -

    When pressed, open a system dialog box asking the user to confirm the action

    -
  2. -
  3. -

    If confirmed, make a POST request to the deletion endpoint, and append the response to the parent <item>.

    -
  4. -
-
-
-

Now when we press “Delete”, we get the confirmation dialog as expected. -After pressing confirm, the backend response triggers behaviors that show a confirmation toast and reload the list of contacts. -The item for the deleted contact disappears from the list.

-
-
-
-screenshot hyperview swipe delete -
-
Figure 21. Delete from swipe button
-
-
-

Notice that the action buttons are able to support any type of behavior action, from push to alert. -If we wanted to, we could have the action buttons trigger our custom actions, like open-phone and open-email. -Custom components and actions can be mixed freely with the standard components and actions that come standard with the Hyperview framework. -This makes the extensions to the Hyperview client feel like first-class features.

-
-
-

In fact, I’ll let you in on a secret. -Within the Hyperview client, standard components and actions are implemented the same way as custom components and actions! -The rendering code does not treat <view> differently from <swipe:row>. -The behavior code does not treat alert differently from open-phone. -They are both implemented using the same techniques described in this section. -Standard components and actions are just the ones that are universally needed by all mobile apps. -But they are just the starting point. -Most mobile apps will require some extensions to the Hyperview client to deliver a great user experience. -Extensions evolve the client from being a generic “Hyperview client”, to being a purpose-built client for your app. -And importantly, this evolution preserves the Hypermedia, server-driven architecture and all of its benefits.

-
-
-
-
-
-

14.4. Conclusion

-
-
    -
  • -

    With custom components and behaviors, Hyperview apps can do anything a native app can do.

    -
  • -
  • -

    Support for system actions (like SMS and email) can be added with a Hyperview behavior action.

    -
  • -
  • -

    Support for high-level UIs (like confirmation toasts) can also be added with a Hyperview behavior action.

    -
  • -
  • -

    Support for customized screen elements (like swipeable items in lists) can be added with Hyperview custom components.

    -
  • -
  • -

    The standard behaviors and components that come with the Hyperview client are implemented the same way as custom behavior actions and components.

    -
  • -
  • -

    By customizing the Hyperview client, developers can build a mobile app that suits their specific needs while retaining the benefits of a thin-client, hypermedia architecture.

    -
  • -
-
-
-
-
-

Conclusion

-
-

15. Conclusion

-
-
-

Hypermedia Reconsidered

-
-

We hope that in this book we have managed to convince you that hypermedia, rather than being a “legacy” technology -or a technology only appropriate for “documents” of links, text and pictures, is, in fact, a powerful technology for -building applications. In this book you have seen how to build sophisticated user interfaces for both the web, with htmx, -and for a mobile application, using Hyperview, using hypermedia as a core underlying application technology.

-
-
-

Focusing in on the web, in particular, many developers today view the links and forms of “plain” HTML as bygone tools -from a less sophisticated age. And, in some ways, they are right: there were definite usability issues with the -original web. However, as we discovered while building Contact.app, by simply addressing four core limitations of -HTML:

-
-
-
    -
  • -

    By making any element capable of issuing an HTTP request

    -
  • -
  • -

    By making any event capable of triggering an HTTP event

    -
  • -
  • -

    By making all the different types of HTTP methods available

    -
  • -
  • -

    By making it possible to target any element in the DOM for replacement

    -
  • -
-
-
-

We were able to build user interfaces that many developers would assume require a significant amount of client-side -JavaScript, but using only hypermedia concepts.

-
-
-

The Hypermedia Driven Application approach is not right for every application (after all, no approach is right for -every application) but, for many applications, the increased flexibility and simplicity of hypermedia can be a huge -benefit. And, even if your application wouldn’t benefit from this approach, it is at least worthwhile to understand -the approach, its strengths and weaknesses, and how it differs from the approach you are taking. The original web -grew faster than any distributed system in history, and it is worth understanding the underlying technologies that -made that growth possible.

-
-
-
-

15.1. Pausing, And Reflecting

-
-

The JavaScript community and, by extension, the web development community is famously chaotic, with new frameworks and -technologies emerging monthly, and sometimes even weekly. It can be exhausting to keep up with the latest and -greatest technologies that are coming out, and, at the same time, terrifying that we won’t keep up with them and -be left behind in our career.

-
-
-

This is not a fear without foundation: there are many senior software engineers that have seen their career peter out -because they picked a technology to specialize in that, fairly or not, did not thrive. The web development world tends -to be young, with many companies favoring young developers over older developers who “haven’t kept up.”

-
-
-

We shouldn’t sugar-coat these realities of our industry. On the other hand, we also shouldn’t ignore the downside that -these realities create. It creates a high-pressure environment where everyone is watching for “the new new” thing, that -is, for the latest and greatest technology that is going to change everything. It creates pressure to claim that your -technology is going to change everything, if you are trying to get attention. It tends to favor sophistication over -simplicity. People are scared to ask “Is this too complex?” because it sounds an awful lot like “I’m not smart enough -to understand this.”

-
-
-

The software industry has tended, especially in web development, to lean far more towards innovating, rather than -understanding the technologies of the past and building on them or within them. We tend to look ahead for new, genius -solutions, rather than looking backwards to older ideas. This is understandable: the technology world is necessarily -a forward-looking industry.

-
-
-

On the other hand, there have been a lot of great ideas in the past, many of which have been discarded. We are old enough -to have seen hypermedia come and go as the “new new” idea. It was a little shocking to us to see it discarded so cavalierly -by the industry. Fortunately, the concepts are still sitting there, waiting to be discovered and reinvigorated by -a new generation of web developers. The original, RESTful architecture of the web, when looked at with fresh eyes, -can address many of the problems today’s web developers are facing.

-
-
-

Perhaps, following Mark Twain’s advice, it is time to pause and reflect. Perhaps, for a few quiet moments, we can -put the endless swirl of the “new new” aside, look back on where the web came from, and learn.

-
-
-

Perhaps it’s time to give hypermedia a chance.

-
-
-

We hope you will.

-
-
-
-
+
+
+
+
    +
  1. +

    When pressed, open a system dialog box asking the user to confirm the action

    +
  2. +
  3. +

    If confirmed, make a POST request to the deletion endpoint, and append the + response to the parent <item>.

    +
  4. +
+
+
+

Now when we press “Delete”, we get the confirmation dialog as expected. + After pressing confirm, the backend response triggers behaviors that show a confirmation + toast and reload the list of contacts. + The item for the deleted contact disappears from the list.

+
+
+
+ screenshot hyperview swipe delete +
+
Figure 21. Delete from swipe button
+
+
+

Notice that the action buttons are able to support any type of behavior action, from + push to alert. + If we wanted to, we could have the action buttons trigger our custom actions, like + open-phone and open-email. + Custom components and actions can be mixed freely with the standard components and + actions that come standard with the Hyperview framework. + This makes the extensions to the Hyperview client feel like first-class features.

+
+
+

In fact, I’ll let you in on a secret. + Within the Hyperview client, standard components and actions are implemented the same + way as custom components and actions! + The rendering code does not treat <view> differently from + <swipe:row>. + The behavior code does not treat alert differently from + open-phone. + They are both implemented using the same techniques described in this section. + Standard components and actions are just the ones that are universally needed by all + mobile apps. + But they are just the starting point. + Most mobile apps will require some extensions to the Hyperview client to deliver a great + user experience. + Extensions evolve the client from being a generic “Hyperview client”, to + being a purpose-built client for your app. + And importantly, this evolution preserves the Hypermedia, server-driven architecture and + all of its benefits.

+
+
+
+
+
+

14.4. Conclusion

+
+
    +
  • +

    With custom components and behaviors, Hyperview apps can do anything a native app can do. +

    +
  • +
  • +

    Support for system actions (like SMS and email) can be added with a Hyperview behavior + action.

    +
  • +
  • +

    Support for high-level UIs (like confirmation toasts) can also be added with a Hyperview + behavior action.

    +
  • +
  • +

    Support for customized screen elements (like swipeable items in lists) can be added with + Hyperview custom components.

    +
  • +
  • +

    The standard behaviors and components that come with the Hyperview client are implemented + the same way as custom behavior actions and components.

    +
  • +
  • +

    By customizing the Hyperview client, developers can build a mobile app that suits their + specific needs while retaining the benefits of a thin-client, hypermedia architecture. +

    +
  • +
+
+
+
+
+

Conclusion

+
+

15. Conclusion

+
+
+

Hypermedia Reconsidered

+
+

We hope that in this book we have managed to convince you that hypermedia, rather than being a + “legacy” technology + or a technology only appropriate for “documents” of links, text and pictures, is, in + fact, a powerful technology for + building applications. In this book you have seen how to build sophisticated user + interfaces for both the web, with htmx, + and for a mobile application, using Hyperview, using hypermedia as a core underlying application + technology.

+
+
+

Focusing in on the web, in particular, many developers today view the links and forms of + “plain” HTML as bygone tools + from a less sophisticated age. And, in some ways, they are right: there were definite usability + issues with the + original web. However, as we discovered while building Contact.app, by simply addressing four + core limitations of + HTML:

+
+
+
    +
  • +

    By making any element capable of issuing an HTTP request

    +
  • +
  • +

    By making any event capable of triggering an HTTP event

    +
  • +
  • +

    By making all the different types of HTTP methods available

    +
  • +
  • +

    By making it possible to target any element in the DOM for replacement

    +
  • +
+
+
+

We were able to build user interfaces that many developers would assume require a significant + amount of client-side + JavaScript, but using only hypermedia concepts.

+
+
+

The Hypermedia Driven Application approach is not right for every application (after all, no + approach is right for + every application) but, for many applications, the increased flexibility and simplicity + of hypermedia can be a huge + benefit. And, even if your application wouldn’t benefit from this approach, it is at least + worthwhile to understand + the approach, its strengths and weaknesses, and how it differs from the approach you are taking. + The original web + grew faster than any distributed system in history, and it is worth understanding the underlying + technologies that + made that growth possible. +

+
+
+
+

15.1. Pausing, And Reflecting

+
+

The JavaScript community and, by extension, the web development community is famously chaotic, + with new frameworks and + technologies emerging monthly, and sometimes even weekly. It can be exhausting to keep + up with the latest and + greatest technologies that are coming out, and, at the same time, terrifying that we + won’t keep up with them and + be left behind in our career.

+
+
+

This is not a fear without foundation: there are many senior software engineers that have seen + their career peter out + because they picked a technology to specialize in that, fairly or not, did not thrive. The web + development world tends + to be young, with many companies favoring young developers over older developers who + “haven’t kept up.”

+
+
+

We shouldn’t sugar-coat these realities of our industry. On the other hand, we also + shouldn’t ignore the downside that + these realities create. It creates a high-pressure environment where everyone is watching for + “the new new” thing, that + is, for the latest and greatest technology that is going to change everything. It creates + pressure to claim that your + technology is going to change everything, if you are trying to get attention. It tends to favor + sophistication over + simplicity. People are scared to ask “Is this too complex?” because it + sounds an awful lot like “I’m not smart enough + to understand this.” +

+
+
+

The software industry has tended, especially in web development, to lean far more towards + innovating, rather than + understanding the technologies of the past and building on them or within them. We tend to look + ahead for new, genius + solutions, rather than looking backwards to older ideas. This is understandable: the technology + world is necessarily + a forward-looking industry.

+
+
+

On the other hand, there have been a lot of great ideas in the past, many of which have been + discarded. We are old enough + to have seen hypermedia come and go as the “new new” idea. It was a little shocking + to us to see it discarded so cavalierly + by the industry. Fortunately, the concepts are still sitting there, waiting to be discovered and + reinvigorated by + a new generation of web developers. The original, RESTful architecture of the web, when looked + at with fresh eyes, + can address many of the problems today’s web developers are facing.

+
+
+

Perhaps, following Mark Twain’s advice, it is time to pause and reflect. Perhaps, for a few + quiet moments, we can + put the endless swirl of the “new new” aside, look back on where the web came from, + and learn.

+
+
+

Perhaps it’s time to give hypermedia a chance.

+
+
+

We hope you will.

+
+
+
+
- \ No newline at end of file + \ No newline at end of file