Merge pull request #63 from bigskysoftware/pullout-test

Pullout test
This commit is contained in:
codetalcott 2023-04-20 11:05:38 -04:00 committed by GitHub
commit c4c84ffd0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 693 additions and 942 deletions

View File

@ -144,3 +144,19 @@ about it.
____
Remember the message? “The future is not set. There is no fate but what we make for ourselves.”
____
[.design-note]
.HTML Notes: Why Effective HTML?
****
At the end of each chapter you will find tips and best practices for writing effective HTML.
It's very easy (and sometimes acceptable) to produce mediocre HTML that _seems to_ work,
and many websites settle with _seeming to_ work.
But 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 refactoring HTML as opposed to heaping JavaScript and ARIA attributes over everything.
// TODO: check last sentence here; pulled together from various paragraphs
Effective HTML typically loads faster, is easier to read and debug, performs better both with search engine ranking and screen reader accessibility, and can be scraped programatically.
****

View File

@ -130,7 +130,7 @@ 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,
We will discuss Fielding's key ideas in depth in Chapter 2, and try to correct the record with respect to REST,
HATEOAS and hypermedia.
== The World's Most Successful Hypertext: HTML
@ -198,7 +198,7 @@ When the link is clicked the browser (or, as we sometimes refer to it, the _hype
`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.
in the form of headers. We will discuss these headers, and HTTP in more depth in Chapter 2.
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
@ -695,3 +695,20 @@ 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.
[.design-note]
.HTML Note: Stay Close to the Output
****
[quote, Manuel Matuzović, 'https://www.matuzo.at/blog/2023/single-page-applications-criticism[Why I\'m not the biggest fan of Single Page Applications]']
The fact that the HTML document is something that you barely touch, because everything you need in there will be injected via JavaScript, puts the document and the page structure out of focus.
In order to avoid `<div>` soup (or Markdown soup, or Component soup), you need to be aware of the markup you're producing and be able to change it.
Some SPA frameworks, and some web components, make this more difficult by putting layers of abstraction between the code the developer writes and the generated markup.
While these abstractions can allow developers to create richer UI or work faster,
their pervasiveness means that developers can lose sight of the actual HTML (and JavaScript) being sent to clients.
Without diligent testing, this leads to inaccessibility, poor SEO, and bloat.
****

View File

@ -205,7 +205,7 @@ HTTP (which is the Hypertext Transfer Protocol, after all!): if you wish to issu
applications.
This is an obvious shortcoming of HTML as a hypermedia; it would be wonderful to see this fixed in the
HTML specification. For now, we'll discuss ways to get around this in Chapter 5.
HTML specification. For now, we'll discuss ways to get around this in Chapter 4.
==== HTTP response codes
@ -779,7 +779,7 @@ 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. (JSON Data API's certainly have their place; in Chapter 11 we'll discuss when and how to use them).
hypermedia exchanges rather than, for example, JSON data exchanges, and so on. (JSON Data API's certainly have their place; in Chapter 10 we'll discuss when and how to use them).
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
@ -794,3 +794,43 @@ systems so flexible.
If you were not aware of the full significance of REST and HATEOAS 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 understand the
special nature of HTML, hypermedia and the web!
[.design-note]
.HTML Note: <div> Soup
****
The best-known kind of messy HTML is `<div>` soup.
When developers fall back on the generic `<div>` and `<span>` elements instead of more meaningful tags,
we either degrade the quality of our websites or create more work for ourselves -- probably both.
For example, instead of adding a button using the dedicated `<button>` element,
a `<div>` element might have a `click` event listener added to it.
[source,html]
----
<div class="bg-accent padding-4 rounded-2" onclick="doStuff()">Do stuff</div>
----
There are two main issues with this button:
* It's not focusable -- the Tab key won't get you to it.
* There's no way for assistive tools to tell that it's a button.
Yes, we can fix that by adding `role="button"`` and `tabindex="0"`:
[source,html]
----
<div class="bg-accent padding-4 rounded-2"
role="button"
tabindex="0"
onclick="doStuff()">Do stuff</div>
----
These are easy fixes, but they're things you have to _remember_.
It's not obvious from the HTML source that this is a button,
making the source harder to read and the absence of these attributes harder to spot.
The source code of pages with div soup is difficult to edit and debug.
To avoid div soup, learn the meaning of available tags 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).
****

View File

@ -1,6 +1,6 @@
= A Web 1.0 Application
:chapter: 04
:chapter: 03
:url: ./a-web-1-0-application/
To start our journey into Hypermedia-Driven Applications, we are going to create a simple contact management web
@ -438,7 +438,7 @@ In the first line of code we create a form that will submit back _to the same pa
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
We then have a label (always a good practice!) 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.
@ -859,3 +859,17 @@ No. No it isn't.
It turns out that we can improve the user experience of this application within the
hypermedia architecture. In the next few chapters we will look at https://htmx.org[htmx], a hypermedia-oriented library
that will let us improve our contact application _without_ abandoning the hypermedia approach we have used so far.
[.design-note]
.HTML Note: Semantic HTML
****
[quote,https://t-ravis.com/post/doc/semantic_the_8_letter_s-word/]
I think being asked to write _meaningful_ HTML better lights the path to realizing that it isn't about what the text means to humans--it's about using tags for the purpose outlined in the specs to meet the needs of software like browsers, assistive technologies, and search engines.
Telling people to "use semantic HTML" instead of "read the spec" has led to a lot of people guessing at the meaning of tags -- "`looks pretty semantic to me!" -- instead of engaging with the spec.
We recommend talking about, and writing, _conformant_ HTML.
Use the elements to the full extent provided by the HTML specification,
and let the software take from it whatever meaning they can.
****

View File

@ -1,720 +0,0 @@
= Effective HTML
:chapter: 03
:url: ./effective-html/
WARNING: This chapter is a work in progress.
[quote, Marshall McLuhan]
The medium is the message.
If you're here, you can probably write some HTML.
The web is the #1 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.
[quote, Roy Fielding, https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven#comment-718]
____
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.
____
HTML, like all hypermedia, blurs 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.
An HTML file is not a program that produces a human-readable document.
It _is_ the document.
Unfortunately, HTML's development in terms of hypermedia controls has stagnated
and what little there is often not put to full use.
This chapter looks at HTML as something worth studying in its own right, even in this day and age.
It covers our best practices for writing/generating HTML,
and why HTML is something far cooler than a programming language.
It won't be an HTML tutorial, as that would take a whole other book, but it can accompany you in your HTML re-learning journey.
== 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.footnote:[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>`.
- 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.footnote:[https://adrianroselli.com/2022/11/accessibility-gaps-in-mvps.html] Is an inaccessible product "`viable`"?
- Websites, including websites containing public data or results of publicly-funded research, are impossible to scrape programmatically.
- Search engines have a hard time extracting useful information from a page, and rank that page lower as a result.
In the rest of the chapter, we'll look at these issues in more detail and see how effective HTML can help us develop better websites.
However, we should first note that HTML is not a panacea.
If you care about machine readability, or human readability, or page weight, the most important thing to do is **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).
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 refactoring HTML as opposed to heaping JavaScript and ARIA attributes over everything.
Knowing HTML well might not absolve you from doing your job, but it makes it a lot easier.
"`But I already know HTML well.`"
Maybe you do.
But many people underestimate how sophisticated HTML is.
Indeed, it's very easy (and sometimes acceptable) to produce mediocre HTML that _seems to_ work,
and many websites settle with _seeming to_ work.
But better websites are possible, and anyone can learn HTML to the level of making websites that _actually_ work.
== `<div>` soup
While programming code is described as spaghetti when it's not well organized,
the food of choice for messy markup is soupfootnote:[hence BeautifulSoup, the web scraping library.].
HTML can turn into soup in a variety of ways,
usually due to a disregard for or misunderstanding of best practice
or due to an excess of layers between the developer and the HTML.
The best-known kind of messy HTML is `<div>` soup.
When developers fall back on the generic `<div>` and `<span>` elements instead of more meaningful tags,
we either degrade the quality of our websites or create more work for ourselves -- probably both.
For example, instead of adding a button using the dedicated `<button>` element,
a `<div>` element might have a `click` event listener added to it.
[source,html]
----
<div class="bg-accent padding-4 rounded-2" onclick="doStuff()">Do stuff</div>
----
Why might a developer do this, when the `<button>` element is right there?
There could be a few reasons:
* *Styling.* The `<button>` element might have been harder to style.
* *Confusion.* The button might have some other interactive features, which could lead to the developer not realizing it was a button.
* *Apathy.* The developer doesn't care, and uses `<div>` except when he has to.
It's absolutely possible to implement this button, and indeed most kinds of UI, using nothing but `<div>`.
However, it makes the job harder.
There are two main issues with this button:
* It's not focusable -- the Tab key won't get you to it.
* There's no way for assistive tools to tell that it's a button.
Let's fix that:
[source,html]
----
<div class="bg-accent padding-4 rounded-2"
role="button" <1>
tabindex="0" <2>
onclick="doStuff()">Do stuff</div>
----
<1> Expose this element as a button.
<2> Make this element focusable.
Of course, these are both easy fixes but they're things you have to _remember_.
It's not obvious from the HTML source that this is a button,
making the source harder to read and the absence of these attributes harder to spot.
Furthermore, even if you always remember to add the correct attributes and JavaScript
-- and yes, if you're making almost anything more complex than a button, you will need a lot of JavaScript --
you've made your HTML less readable for little reason.
The source code of pages with div soup is difficult to edit and debug.
We often _don't_ remember to look out for these types of UX and accessibility bugs.
[.dfn]_F5-Driven Development_ is the way most of us write HTML:
write something, _Alt-Tab_ to the browser to see if it works, and go back to edit.
It's a fast and enjoyable way to build things,
but it means that during most of development,
developers are biased towards their own UI needs,
and users (who might use websites differently) become an afterthought.
However, if we use HTML effectively, we can catch many of these issues before they ever occur, even before testing.
Given all this, why are so many developers writing div soup?
There is a tendency to understate the sophistication of HTML.
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).
[.dfn]_Markdown soup_ is the lesser known sibling of `<div>` soup.
This is the result of web developers limiting themselves to the set of elements that the Markdown language provides shorthand for,
even when these elements are incorrect.
Consider the following example of an IEEE-style citation:
[source,markdown]
----
[1] C.H. Gross, A. Stepinski, and D. Akşimşek, <1>
_Hypermedia Systems_, <2>
Bozeman, MT, USA: Big Sky Software.
Available: <https://hypermedia.systems/>
----
<1> The reference number is written in brackets.
<2> Underscores around the book title creates an <em> element.
Here, <em> is used because it's the only Markdown element that is presented in italics by default.
This indicates that the book title is being stressed, but the purpose is to mark it as the title of a work.
HTML has the `<cite>` element that's intended for this exact purpose.
Furthermore, even though this is a numbered list perfect for the `<ol>` element, which Markdown supports, plain text is used for the reference numbers instead.
Why could this be?
The IEEE citation style requires that these numbers are presented in square brackets.
This could be achieved on an `<ol>` with CSS,
but Markdown doesn't have a way to add a class to elements meaning the square brackets would apply to all ordered lists.
[.info]
****
We call `<ol>` and `<ul>` "ordered" and "unordered" lists. Both are actually ordered, however, and the difference is whether the place of a particular element is significant. For instance, instructions should usually be marked up with `<ol>` since it may contain references like "Repeat steps 3 to 5". If we were using a style like APA in our reference list, where citations are not referenced by number, we would use an `<ul>`.
****
Don't shy away from using embedded HTML in Markdown.
For larger sites, also consider Markdown extensions.
[source,markdown]
----
{.ieee-reference-list} <1>
1. C.H. Gross, A. Stepinski, and D. Akşimşek, <2>
<cite>Hypermedia Systems</cite>, <3>
Bozeman, MT, USA: Big Sky Software.
Available: <https://hypermedia.systems/>
----
<1> Many Markdown dialects let us add ids, classes and attributes using curly braces.
<2> We can now use the <ol> element, and create the brackets in CSS.
<3> We use `<cite>` to mark the title of the work being cited (not the whole citation!)
You can also use custom processors to produce extra-detailed HTML instead of writing it by hand:
[source,markdown]
----
{% reference_list %} <1>
[hypers2023]: <2>
C.H. Gross, A. Stepinski, and D. Akşimşek, _Hypermedia Systems_,
Bozeman, MT, USA: Big Sky Software, 2023.
Available: <https://hypermedia.systems/>
{% end %}
----
<1> `reference_list` is a macro that will transform the plain text to highly-detailed HTML.
<2> A processor can also resolve identifiers, so we don't have to manually keep the reference list in order and the in-text citations in sync.
=== Remedy: Stay close to the output
[quote, Manuel Matuzović, 'https://www.matuzo.at/blog/2023/single-page-applications-criticism[Why I\'m not the biggest fan of Single Page Applications]']
The fact that the HTML document is something that you barely touch, because everything you need in there will be injected via JavaScript, puts the document and the page structure out of focus.
In order to avoid `<div>` soup (or Markdown soup, or similar), you need to constantly be aware what kind of markup you're producing and be able to change it.
Web frameworks, particularly SPA frameworksfootnote:[
This also applies to frameworks like Next and Remix that use SPA technologies like React to render static HTML.],
can have a tall tower of abstraction between the code the developer writes and the generated markup.
While these abstractions can allow developers to create richer UI or work faster,
their pervasiveness means that they can lose sight of the actual HTML (and JavaScript) being sent to clients.
Without diligent testing, this leads to inaccessibility, poor SEO, and bloat.
For example, a popular concept found in many frameworks is *components*.
Components encapsulate a section of a page along with its dynamic behavior.
While encapsulating behavior is a good way to organize code,
they also separate elements from their surrounding context,
which can lead to wrong or inadequate relationships between elements.
The result is what one might call [.dfn]_component soup_,
where information is hidden in component state,
rather than being present in the HTML, which is now incomprehensible due to missing context.
In our Client Side Scripting chapter, we'll look at alternatives to component-based frameworks that can be used to avoid these shortcomings.
To be abundantly clear, components aren't the cause of all div soup.
Not even most of it.
The root cause is the fact that HTML is falsely believed to be very simple,
and as a result, developers and organizations don't invest in learning and applying HTML skills.
However, don't reach for components for reuse without considering other options.
Lower-level mechanisms usually (allow you to) produce better HTML.
Components, when used well, can actually _improve_ the clarity of your HTML.
To decide if a component is appropriate for your use case, a good rule of thumb is to ask:
"`Could this reasonably be a built-in HTML element?`"
For example, a code editor is a good candidate,
since HTML already has `<textarea>` and `contenteditable` elements.
In addition, a fully-featured code editor will have many child elements that won't provide much information anyway.
We can use features like
link:https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM[Shadow DOM]
to encapsulate these elementsfootnote:[
Beware that Shadow DOM is a newer web platform feature that's still in development at the time of writing.
In particular, there are some accessibility bugs that may occur when elements inside and outside the shadow root interact.].
We can create a
link:https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements[custom element],
`<code-area>`, that we can drop into our page whenever we want.
See how we're _extending_ HTML, rather than abstracting it away.
.Web Components
****
Web Components is the collective name of a few standards;
Custom Elements and Shadow DOM, and `<template>` and `<slot>`.
All of these standards bring useful capabilities to the table.
`<template>` elements remove their contents from the document, while still parsing them as HTML (unlike comments) and making them accessible to JavaScript.
Custom Elements let us initialize and tear down behaviors when elements are added or removed, which would previously require manual work or MutationObservers.
Shadow DOM lets us encapsulate elements, leaving the "light" (non-shadow) DOM clean.
However, trying to actually reap these benefits is often frustrating.
Some of these are simply growing pains of new standards
(like the many accessibility problems of Shadow DOM)
that are actively being worked on.
Others are the result of Web Components trying to be too many things at the same time:
* An extension mechanism for HTML. To this end, each custom element is a tag we add to the language.
* A lifecycle mechanism for behaviors. Methods like `createdCallback`, `connectedCallback`, etc. allow behavior to be added to elements without needing to be manually invoked when those elements are added.
* A unit of encapsulation. Shadow DOM insulates elements from their surroundings.
The result is that if you want any one of these things,
the others come along for the ride.
If you want to attach some behaviors to some elements using lifecycle callbacks,
you need to create a new tag,
which means you can't have multiple behaviors on one element,
and you isolate elements you add from elements already in the page,
which is a problem if they need to have ARIA relationships.
****
== HTML5 soup
"`Yeah! Down with `<div>`! It's time to use the full power of *HTML5!*`"
Be careful.
Elements like `<section>`, `<article>`, `<nav>`, `<header>`, `<footer>`, `<figure>` have become a sort of shorthand for HTML.
Developers may sprinkle them generously and haphazardly over `<div>` soup.
This is not an improvement, and can in fact make a website worse.
By using these elements, a page makes false promises, like `<article>` elements being self-contained, reusable entities, to clients like browsers, search engines and scrapers that can't know better.
.HTMHell, [.cite]##10 <section> is no replacement for <div>#, https://www.htmhell.dev/10-section-is-no-replacement-for-div/
[source,html]
----
<section id="page-top">
<section data-section-id="page-top" style="display: none;"></section>
</section>
<main>
<section id="main-content">
<header id="main-header">
<h1>...</h1>
<section class="container-fluid">
<section class="row">
<article class="content col-sm-12">
<section class="content-inner">
<div class="content__body">
<article class="slider">
<section class="slide"> … </section>
</article>
</div>
</section>
</article>
</section>
</section>
</header>
</section>
</main>
----
Most HTML isn't this much of a mess,
but it's far too common for `<section>` and `<article>` to be used as a drop-in replacement `<div>` instead of adding useful information.
To avoid this:
* Check the HTML spec. Make sure that the element you're using fits your use case.
* Don't try to be specific when you can't or don't need to.
Sometimes, `<div>` is fine.
=== Keep the spec on hand
[quote,Confucius]
The beginning of wisdom is to call things by their right names.
The most authoritative (though not necessarily best) resource for learning about HTML is the HTML specification.
The current specification lives on link:https://html.spec.whatwg.org/multipage[].footnote:[
The single-page version is too slow to load and render on most computers.
There's also a developers' edition at /dev, but I prefer the styling of the standard version.]
There's no need to rely on hearsay to keep up with developments in HTML.
Section 4 features a list of all available elements,
including what they represent, where they can occur, and what they are allowed to contain.
It even tells you when you're allowed to leave out closing tags!
[source,html]
----
<!doctype html>
<html lang=en>
This is a valid HTML document.
----
[.info]
****
The `<html>` tag can also be omitted (since it's obvious that an HTML document with an HTML doctype is HTML) but it's bad practice to leave out the opening tag since it holds the `lang` attribute which specifies the natural language of the document.
****
Section 4 in particular is a great piece of reference material and an useful read in general.
Reading it through (skipping over the implementation details, like the several pages of algorithms)
will give you a sense of how HTML is intended to be written.
=== Remedy: Know your budget
The close relationship between the content and the markup means that
good HTML is actually quite labor-intensive, often across a whole organization.
Most sites have a separation between the authors,
who are rarely familiar with HTML and _very_ rarely want to think about it,
and the developers, who need to develop a generic system able to handle any content that's thrown at it --
this separation usually taking the form of a CMS.
As a result, having markup tailored to content, which is often necessary for advanced HTML, is rarely feasible.
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 contains the most conformant HTML it can.
What's most important is to avoid _wrong_ HTML -- it can be better to fall back on a more generic element than to be precisely incorrect.
The kinds of defects caused by _inadequate_ HTML can usually be caught through testing.
If you have the resources, however, putting more care in your HTML will produce a more polished site.
Much like style guides, well-written HTML gives an air of quality and prestige to a document, even if few notice it.
When it comes to HTML, you get what you pay for.
== The S word
[quote, '_Mean Girls_ (2004)']
Gretchen, stop trying to make fetch happen! It's not going to happen!
You might have noticed how we've avoided the use of the word "`semantic`" so far, partly because many people associate it with annoying pedantic colleagues (couldn't be us!), and partly because it has multiple meanings, only one of which we care about.
We're not really about the "`Semantic Web`".
The "Semantic Web" was a vision of a system that could both express any kind of human knowledge, and be useful for computing.
It planned to achieve this using _ontologies_, repositories of schemas like "person", "movie" and "species" and relations like "named", "part of" and "created by".
The problem with this vision is that information on the Web rarely fits into neat categories.
Because no single ontology can be defined that encapsulates all kinds of information one might wish to publish on the Web,
Semantic Web systems need to be pluggable with different schemas.
In turn, a Semantic Web client, in order to do something useful with an arbitrary piece of HTML, needs to be able to parse these schemas, which means we need to define a standard machine-readable format for ontologies.
But a single format couldn't express every kind of object and relation...
It's turtles all the way down.
In practice, most implementations stop at the topmost turtle.
Ontologies are defined in natural language,
and clients are hardcoded to support a fixed set of schemas.
The requirement for prior agreement between server and client means this technology does not have the generality of the Web,
and for most use cases, you might as well define a JSON API.
Instead of extensibility through custom namespaces,
HTML is extensible through its flexibility --
both its tolerance for errors and its well-defined extension points like classes and `data-` attributes.
These affordances let us embed metadata in it without native support.
They all have the possibility of name collisions,
but fragility and messiness is ultimately unavoidable for a generalized human information exchange language.
Tag and attribute names in such a language are not _identifiers_ for behavior -- like function names in a programing language -- but _words_ with well-understood meanings.
No amount of namespacing can make fetch happen,
and developers should be able to deal with that.
Embrace the mess and let go of your schemas.
A flexible format -- not an infinity of namespaces with URLs pointing to nothing -- is "`software design on the scale of decades`".
***
This is a necessarily reductive explanation of the Semantic Web, a field that we've described in past tense even though it continues to have some practical use.
The reason it doesn't matter to us is because *the Semantic Web has nothing to do with semantic HTML.*
Semantic HTML has no ambitions of robotic agents navigating information and helping us make connections and discoveries.
It's actually quite mundane: _don't break the web._
[quote,https://t-ravis.com/post/doc/semantic_the_8_letter_s-word/]
I think being asked to write _meaningful_ HTML better lights the path to realizing that it isn't about what the text means to humans--it's about using tags for the purpose outlined in the specs to meet the needs of software like browsers, assistive technologies, and search engines.
Telling people to "use semantic HTML" instead of "read the spec" has led to a lot of people guessing at the meaning of tags -- "`looks pretty semantic to me!" -- instead of engaging with the spec.
I think even "`meaningful`" is too lofty.
Instead, I recommend talking about, and writing, **conformant** HTML.
Use the elements to the full extent provided by the HTML specification,
and let the software take from it whatever meaning they can.
Speaking of assistive technologies, by the way...
== The A word
Throughout this chapter, we've gestured at potential accessibility benefits to be had from effective HTML.
[quote, Manuel Matuzović, 'https://alistapart.com/article/my-accessibility-journey-what-ive-learned-so-far/[My Accessibility Journey: What Ive Learned So Far]']
(Re)learning HTML and using it consciously prevents and fixes many accessibility issues.
It's true that all else being equal, an app that makes full use of HTML will be more accessible than one that is made of soup.
However, HTML is not a panacea.
Even the adage that HTML is "`accessible by default`" is a bit misleading.
If you use simple HTML strictly as recommended by the spec, you are unlikely to run into accessibility bugs (though it does happen, because this is web development).
Problems often arise when we try to implement controls that aren't built into HTML.
Sometimes, JavaScript is necessary to implement a UI control in an accessible manner
(that is, in a way that supports all required keyboard interaction and presenting state to assistive tools).
Clever "tricks" to implement controls in only HTML/CSS are fun, but often not practical.
Take the following code, which uses radio buttons and CSS hacks to create something that resembles tabs:
[source,html]
----
<input type="radio" name="tabs" id="tab1" checked>
<label for="tab1">Tab 1</label> <1>
<input type="radio" name="tabs" id="tab2">
<label for="tab2">Tab 2</label>
<div class="tabpanel" id="tabpanel1">Tab 1 contents</div>
<div class="tabpanel" id="tabpanel2">Tab 2 contents</div>
<style>
input[name="tabs"] { display: none; } <2>
.tabpanel { display: none; }
#tab1:checked ~ #tabpanel1 { display: block; } <3>
#tab2:checked ~ #tabpanel2 { display: block; } <3>
</style>
----
<1> Each tab is an `<input>` and a `<label>`.
<2> Hide the radio buttons. We can actuate them by clicking the associated labels.
<3> A tabpanel will be visible when the associated tab is selected.
This code will "`work.`"
Clicking on the tabs will change the content displayed, and without a single line of JavaScript.
Unfortunately, tabs have requirements beyond clicking to change content.
This implementation has many missing features that will lead to user confusion and frustration, as well as some undesirable behaviors.
From the link:https://www.w3.org/WAI/ARIA/apg/patterns/tabs/[ARIA Authoring Practices Guide on tabs]:
* Keyboard interaction
** The tabs can't be focused with the Tab key. Because the radio buttons are hidden with `display: none`, they are removed from the focus order, and label elements are not focusable.
** "`[...] does not listen for Down Arrow or Up Arrow so those keys can provide their normal browser scrolling functions [...]`"
Radio buttons listen to these events (since they're usually presented vertically). Thankfully, right and left arrow keys also work.
* ARIA roles, states, and properties
** "`[The element that contains the tabs] has role `tablist`.`"
There is no such element in this implementation, as that would break the CSS.
** "`Each [tab] has role `tab` [...]`"
The tab elements have role `label`. Furthermore, the elements they are labeling are hidden.
** "`Each element that contains the content panel for a `tab` has role `tabpanel`.`"
No, though that could be added.
** "`Each [tab] has the property `aria-controls` referring to its associated tabpanel element.`"
Nope.
** "`The active `tab` element has the state `aria-selected` set to `true` and all other `tab` elements have it set to `false`.`"
Nope.
** "`Each element with role `tabpanel` has the property `aria-labelledby` referring to its associated `tab` element.`"
No. The element that _is_ labelled by the tab element is a hidden radio button.
[.info]
****
`display: none;` in CSS is not purely cosmetic -- it removes elements from the accessibility tree and keyboard focus. If you want to hide an element visually without hiding it from assistive technology, you can use this utility class:
[source,css]
----
.vh {
clip: rect(0 0 0 0);
clip-path: inset(50%);
block-size: 1px;
inline-size: 1px;
overflow: hidden;
white-space: nowrap;
}
----
`vh` is short for "`visually hidden.`"This class uses multiple methods and workarounds to make sure no browser removes the element's function.
****
It turns out that fulfilling all of these requirements takes a lot of code.
Some of the ARIA attributes can be added directly in HTML,
but they are repetitive
and others (like `aria-selected`) need to be set through JavaScript since they are dynamic.
The keyboard interactions can be error-prone too.
It's not impossible to make a good tab set implementation.
However, it's difficult to trust that a new implementation will work in all environments, since most of us have limited access to testing devices.
This is why it's often recommended to use established libraries for UI interactions instead of rolling your own.
Before adding a dependency, however, let's reconsider our design.
Does the information really need to be presented as tabs?
Sometimes the answer is yes
-- we used dummy text in our code example, so we can't tell --
but if not, a sequence of details disclosures fulfills a very similar purpose.
[source,html]
----
<details><summary>Disclosure 1</summary>
Disclosure 1 contents
</details>
<details><summary>Disclosure 2</summary>
Disclosure 2 contents
</details>
----
Compromising UX just to avoid JavaScript is bad development.
At the same time, the possibilities and constraints of the platform should be considered while designing interfaces.
It might be possible to achieve an equal (or better!) quality of UX while allowing for a simpler and more robust implementation.
But when it's not, we shouldn't try to stuff every problem into an HTML box.
=== Screen reader rage
The purpose of writing good HTML is not to please the specification deities.
It's to make good websites.
The spec is a good starting point when deciding how to mark something up,
but when browser implementations don't conform,
we shouldn't throw up our hands because we did what was specified.
****
After all, browser implementers do a _lot_ of work to deal with developers' broken HTML -- is it such a burden to return the favor?
****
It is of course frustrating when browsers and other tools misbehave.
Accessibility itself feels inaccessible sometimes.
It helps with the frustration is to recognize that hypermedia exchanges are not machine-to-machine communication.
An HTML file is not a program that produces a human-readable document.
It _is_ the document.
So, instead of banging your head against a wall, focus on people, not the tools they use.
Don't write HTML for browsers. or assistive tools, or validators.
HTML is not _for_ them.
HTML is for humans.
== The Scrapeable Web
Hypermedia systems perform best with human-operated clients.
However, machine-readable information can be embedded into HTML pages through a variety of extension mechanisms:
* Classes
* `data-` attributes
* `<meta>` tags
* Link relations (`rel`)
* Microdata (`itemscope`, `itemtype`, `itemprop`)
These mechanisms are fairly unstructured (as per earlier discussion on Semantic Web schematamania),
but structure can be imposed upon them if needed.
One standard for including structured data in HTML is https://microformats.org/[microformats].
Microformats use classes to mark certain elements as containing information to be extracted.
The microformats2 standard uses five kinds of classes:
* `h-` classes denote that an element represents a machine-readable entity, e.g., `h-entry`, `h-resume`
* The other prefixes denote that an element represents properties of an enclosing entity:
** `p-` classes are plain text properties, from an element's inner text or `alt` attribute, e.g., `p-name`, `p-category`
** `u-` classes are URL properties, from an element's `href` or `src`, e.g., `u-url`, `u-email`, `u-photo`
** `dt-` classes are date/time properties, from `<time>` elements, e.g., `dt-published`, `dt-updated`
** `e-` classes are embedded markup properties, from an element's inner HTML, e.g., `e-content`, `e-description`
There are also conventions for extracting common properties like name, URL and photo without needing classes for each property.
By adding these classes into the HTML representation of an object, we allow the properties of the object to be recovered from the HTML. For example, this simple HTML:
[source,html]
----
<a class="h-card" href="https://john.example">
<img src="john.jpg" alt=""> John Doe
</a>
----
can be parsed into this JSON-like structure:
[source,json]
----
{
"type": ["h-card"],
"properties": {
"name": ["John Doe"],
"photo": ["john.jpg"],
"url": ["https://john.example"]
}
}
----
We can see microformats in action by looking back at how we can mark up the reference list we mentioned earlier in this example.
Using a variety of properties and nested objects, we can mark up every bit of information about the work being cited in a machine-readable way:
[source,html]
----
<ol class="reference-list">
<li class="h-cite" id="cite-hypers2023">
<span class="p-author">C.&#8202;H. Gross</span>,
<span class="p-author">A. Stepinski</span>,
and <span class="p-author">D. Akşimşek</span>,
<cite class="p-name">Hypermedia Systems</cite>,
<span class="p-publisher h-card">
<span class="p-adr h-adr">
<span class="p-locality">Bozeman</span>,
<span class="p-region">MT</span>,
<span class="p-country-name">USA</span>
</span>:
<span class="p-name">Big Sky Software</span>
</span>,
<time class="dt-published">2023</time>.
Available:
<a class="u-url" href="https://hypermedia.systems/">
https://hypermedia.systems/
</a>
</li>
</ol>
----
This can be parsed into a JSON-like structure, as follows:
[source,json]
----
{
"type": ["h-cite"],
"properties": {
"author": ["C.\u200aH. Gross", "A. Stepinski", "D. Akşimşek"],
"name": ["Hypermedia Systems"],
"url": ["https://hypermedia.systems/"],
"published": ["2023"],
"publisher": [{
"type": ["h-card"],
"properties": {
"name": ["Big Sky Software"],
"adr": [{
"type": ["h-adr"],
"properties": {
"locality": ["Bozeman"],
"region": ["MT"],
"country-name": ["USA"]
}
}]
}
}]
}
}
----
In this example, Microformats and the extensibility of HTML proved quite useful.
However, embedding data in HTML is hardly appropriate for every use case.
Your human-facing and machine-facing interfaces may end up being limited by each other.
It's often the best option to define a JSON data API separate from your HTML, which will be discussed later in this book.
== Where to next
Unfortunately, a full HTML tutorial is way out of scope for one chapter of this book.
Here are some resources you can check out if you'd like to invest in your HTML knowledge:
* Foundations
* HTML specification: https://html.spec.whatwg.org/multipage
* TODO link resources on alt text.
* https://htmhell.dev: Along with sinister abuses of HTML, this website shares development tips that will help ypu keep up-to-date with best practice.
* Referenced
** Manuel Matuzović, [.cite]_Lost in Translation_, https://www.youtube.com/watch?v=Wno1IhEBTxc.
** Manuel Matuzović, [.cite]_Why I'm not the biggest fan of Single Page Applications_, https://www.matuzo.at/blog/2023/single-page-applications-criticism/
** [.cite]_semantic: the 8 letter s-word_, https://t-ravis.com/post/doc/semantic_the_8_letter_s-word/

View File

@ -1,6 +1,6 @@
= Extending HTML As Hypermedia
:chapter: 05
:chapter: 04
:part: Hypermedia-Driven Web Applications with Htmx
:part_url: ./part/htmx/
:url: ./extending-html-as-hypermedia/
@ -891,3 +891,44 @@ By staying closer to the original model of the web, htmx aims to strike a balanc
deferring to other libraries for more elaborate frontend 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.
[.design-note]
.HTML Note: Caution with Improvised UI Elements
****
Accessibility problems can arise when we try to implement controls that aren't built into HTML.
For example, what if you make something that looks like a set of tabs, but you use radio buttons and CSS hacks to build it?
The problem here is that tabs have requirements beyond clicking to change content.
Your improvised tabs may be missing features that will lead to user confusion and frustration, as well as some undesirable behaviors.
From the link:https://www.w3.org/WAI/ARIA/apg/patterns/tabs/[ARIA Authoring Practices Guide on tabs]:
* Keyboard interaction
** Can the tabs be focused with the Tab key?
* ARIA roles, states, and properties
** "`[The element that contains the tabs] has role `tablist`.`"
** "`Each [tab] has role `tab` [...]`"
** "`Each element that contains the content panel for a `tab` has role `tabpanel`.`"
** "`Each [tab] has the property `aria-controls` referring to its associated tabpanel element.`"
** "`The active `tab` element has the state `aria-selected` set to `true` and all other `tab` elements have it set to `false`.`"
** "`Each element with role `tabpanel` has the property `aria-labelledby` referring to its associated `tab` element.`"
You would need to write a lot of code to make your improvised tabs fulfill all of these requirements. Some of the ARIA attributes can be added directly in HTML,
but they are repetitive
and others (like `aria-selected`) need to be set through JavaScript since they are dynamic.
The keyboard interactions can be error-prone too.
It's not impossible to make your own tab set implementation.
However, it's difficult to trust that a new implementation will work in all environments, since most of us have limited access to testing devices.
Stick with established libraries for UI interactions. If a use case requires an improvised solution, test carefully for keyboard interaction and accessibility.
****

View File

@ -1,6 +1,6 @@
= Htmx Patterns
:chapter: 06
:chapter: 05
:url: ./htmx-in-action/
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
@ -509,7 +509,7 @@ Currently, our application has a small amount of validation that is done entirel
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:
the code for updating a contact looks like from Chapter 3:
[source, python]
.Server-side validation on contact update
@ -1032,3 +1032,34 @@ And, again, we are doing all this within the original, RESTful model of the web.
behavior, we are still exchanging hypermedia with the server, with no JSON API response to be seen.
As the web was designed.
[.design-note]
.Component Soup?
****
Components encapsulate a section of a page along with its dynamic behavior.
While encapsulating behavior is a good way to organize code,
it can also separate elements from their surrounding context,
which can lead to wrong or inadequate relationships between elements.
The result is what one might call [.dfn]_component soup_,
where information is hidden in component state,
rather than being present in the HTML, which is now incomprehensible due to missing context.
Before you reach for components for reuse, consider your options.
Lower-level mechanisms often (allow you to) produce better HTML.
In some cases, components can actually _improve_ the clarity of your HTML.
To decide if a component is appropriate for your use case, a good rule of thumb is to ask:
"`Could this reasonably be a built-in HTML element?`"
For example, a code editor is a good candidate,
since HTML already has `<textarea>` and `contenteditable` elements.
In addition, a fully-featured code editor will have many child elements that won't provide much information anyway.
We can use features like
link:https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM[Shadow DOM]
to encapsulate these elementsfootnote:[
Beware that Shadow DOM is a newer web platform feature that's still in development at the time of writing.
In particular, there are some accessibility bugs that may occur when elements inside and outside the shadow root interact.].
We can create a
link:https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements[custom element],
`<code-area>`, that we can drop into our page whenever we want.
****

View File

@ -1,6 +1,6 @@
= More Htmx Patterns
:chapter: 07
:chapter: 06
:url: ./more-htmx-patterns/
== Active Search
@ -1152,3 +1152,29 @@ 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.
[.design-note]
.HTML Note: HTML5 Soup
****
[quote,Confucius]
The beginning of wisdom is to call things by their right names.
Elements like `<section>`, `<article>`, `<nav>`, `<header>`, `<footer>`, `<figure>` have become a sort of shorthand for HTML.
By using these elements, a page can make false promises, like `<article>` elements being self-contained, reusable entities, to clients like browsers, search engines and scrapers that can't know better. To avoid this:
* Make sure that the element you're using fits your use case. Check the HTML spec.
* Don't try to be specific when you can't or don't need to.
Sometimes, `<div>` is fine.
The most authoritative resource for learning about HTML is the HTML specification.
The current specification lives on link:https://html.spec.whatwg.org/multipage[].footnote:[
The single-page version is too slow to load and render on most computers.
There's also a developers' edition at /dev, but I prefer the styling of the standard version.]
There's no need to rely on hearsay to keep up with developments in HTML.
Section 4 features a list of all available elements,
including what they represent, where they can occur, and what they are allowed to contain.
It even tells you when you're allowed to leave out closing tags!
****

View File

@ -1,6 +1,6 @@
= A Dynamic Archive UI
:chapter: 08
:chapter: 07
:url: ./a-dynamic-archive-ui/
== A Dynamic Archive UI
@ -506,7 +506,7 @@ As nice as this UI is, there is one minor annoyance: as the progress bar updates
Let's think through the cause of the problem. If you're in a hurry to get to an answer, feel free to jump ahead to "`our solution.`"
It turns out that there is a native HTML technology for smoothing out changes on an element from one state to another: the CSS Transitions API, the same that we discussed in Chapter 5. Using CSS Transitions, you can smoothly animate an element between different styling by using the `transition` property.
It turns out that there is a native HTML technology for smoothing out changes on an element from one state to another: the CSS Transitions API, the same that we discussed in Chapter 4. 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: `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
@ -522,7 +522,7 @@ The good news: htmx has a way to rectify this situation.
=== 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
When we discussed the htmx swap model in Chapter 4, we focused on the classes that htmx adds and removes, but we skipped
over the process of "`settling.`" In htmx, settling involves several steps: 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:
@ -561,7 +561,7 @@ Now that, dear reader, does spark joy.
== Dismissing The Download UI
Some users may change their mind, and decide not to download the archive. They may never witness our progress bar, but that's okay. We're going to give these users a button to dismiss the download link and return to the original export UI state.
Some users may change their mind, and decide not to download the archive. They may never witness our progress bar, but that's OK. We're going to give these users a button 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.
@ -603,10 +603,10 @@ alternatives. Currently, 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. 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 chapter 10, but, put briefly: scripting is perfectly
in a Hypermedia-Driven Application in more depth in chapter 9, but, put briefly: scripting is perfectly
acceptable in a HDA, as long as it doesn't replace the core hypermedia mechanics of the application.
For our auto-download feature we will use https://hyperscript.org[+_hyperscript+], our preferred scripting option. JavaScript would also work here, and would be nearly as simple; again, we'll discuss scripting options in Chapter 10.
For our auto-download feature we will use https://hyperscript.org[+_hyperscript+], our preferred scripting option. JavaScript would also work here, and would be nearly as simple; again, we'll discuss scripting options in Chapter 9.
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.
@ -632,3 +632,35 @@ auto-downloading, and we've done nearly all of it -- with the exception of a sma
pure hypermedia. It took about 16 lines of front end code and 16 lines of backend code to build the whole thing.
HTML, with a bit of help from a hypermedia-oriented JavaScript library such as htmx, can in fact be extremely powerful and expressive.
// TODO: work here? better as a vanilla pullout?
[.design-note]
.HTML Notes: On Web Components
****
Web Components is the collective name of a few standards;
Custom Elements and Shadow DOM, and `<template>` and `<slot>`.
All of these standards bring useful capabilities to the table.
`<template>` elements remove their contents from the document, while still parsing them as HTML (unlike comments) and making them accessible to JavaScript.
Custom Elements let us initialize and tear down behaviors when elements are added or removed, which would previously require manual work or MutationObservers.
Shadow DOM lets us encapsulate elements, leaving the "light" (non-shadow) DOM clean.
However, trying to reap these benefits is often frustrating.
Some difficulties are simply growing pains of new standards
(like the accessibility problems of Shadow DOM)
that are actively being worked on.
Others are the result of Web Components trying to be too many things at the same time:
* An extension mechanism for HTML. To this end, each custom element is a tag we add to the language.
* A lifecycle mechanism for behaviors. Methods like `createdCallback`, `connectedCallback`, etc. allow behavior to be added to elements without needing to be manually invoked when those elements are added.
* A unit of encapsulation. Shadow DOM insulates elements from their surroundings.
The result is that if you want any one of these things,
the others come along for the ride.
If you want to attach some behaviors to some elements using lifecycle callbacks,
you need to create a new tag,
which means you can't have multiple behaviors on one element,
and you isolate elements you add from elements already in the page,
which is a problem if they need to have ARIA relationships.
****

View File

@ -1,6 +1,6 @@
= Tricks Of The Htmx Masters
:chapter: 09
:chapter: 08
:url: ./deep-htmx/
@ -45,10 +45,10 @@ options for creating more advanced Hypermedia-Driven Applications.
We'll start with the hx-swap attribute. This is often not included on elements that issue htmx-driven requests because its default behavior -- `innerHTML`, which swaps the inner HTML of the element -- tends to cover most use cases.
We earlier saw situations where we wanted to override the default behavior and use `outerHTML`, for example. And, in chapter 3, we discussed some
We earlier saw situations where we wanted to override the default behavior and use `outerHTML`, for example. And, in chapter 2, 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 chapter 5, 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` offers further control with the following modifiers:
@ -433,7 +433,7 @@ an idea of how the `HX-Trigger` response header can be used to coordinate sophis
== 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
but htmx supports quite a few more headers for both requests and responses. In chapter 4 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:
@ -454,7 +454,7 @@ You can find a reference for all requests and response headers in the https://ht
=== 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
We discussed HTTP Response Codes in Chapter 3. 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:
@ -740,3 +740,25 @@ an example:
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.
[.design-note]
.HTML Notes: Know Your HTML Budget
****
The close relationship between content and markup means that
good HTML is labor-intensive.
Most sites have a separation between the authors,
who are rarely familiar with HTML,
and the developers, who need to develop a generic system able to handle any content that's thrown at it --
this separation usually taking the form of a CMS.
As a result, having markup tailored to content, which is often necessary for advanced HTML, is rarely feasible.
Furthermore, for internationalized sites, content in different languages being injected into the same elements can degrade markup quality as stylistic conventions differ between languages.
It's an expense few organizations can spare.
Thus, we don't expect every site to contain perfectly conformant HTML.
What's most important is to avoid _wrong_ HTML -- it can be better to fall back on a more generic element than to be precisely incorrect.
If you have the resources, however, putting more care in your HTML will produce a more polished site.
****

View File

@ -1,6 +1,6 @@
= Client-Side Scripting
:chapter: 10
:chapter: 09
:url: ./client-side-scripting/
[quote, Roy Fielding, Architectural Styles and the Design of Network-based Software Architectures]
@ -1302,3 +1302,33 @@ we would focus on these more general concerns:
And 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 uses a JSON data API? That's OK.
Just don't make it a habit.
[.design-note]
.HTML Notes: Markdown soup
****
[.dfn]_Markdown soup_ is the lesser known sibling of `<div>` soup.
This is the result of web developers limiting themselves to the set of elements that the Markdown language provides shorthand for,
even when these elements are incorrect.
Consider the following example of an IEEE-style citation:
[source,markdown]
----
[1] C.H. Gross, A. Stepinski, and D. Akşimşek, <1>
_Hypermedia Systems_, <2>
Bozeman, MT, USA: Big Sky Software.
Available: <https://hypermedia.systems/>
----
<1> The reference number is written in brackets.
<2> Underscores around the book title creates an <em> element.
Here, <em> is used because it's the only Markdown element that is presented in italics by default.
This indicates that the book title is being stressed, but the purpose is to mark it as the title of a work.
HTML has the `<cite>` element that's intended for this exact purpose.
Furthermore, even though this is a numbered list perfect for the `<ol>` element, which Markdown supports, plain text is used for the reference numbers instead.
Why could this be?
The IEEE citation style requires that these numbers are presented in square brackets.
This could be achieved on an `<ol>` with CSS,
but Markdown doesn't have a way to add a class to elements meaning the square brackets would apply to all ordered lists.
****

View File

@ -1,6 +1,6 @@
= JSON Data APIs & Hypermedia-Driven Applications
:chapter: 11
:chapter: 10
:url: ./json-data-apis/
[partintro]
@ -503,3 +503,58 @@ 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.
// TODO: does some version of this fit here?
// or, cut if you don't recommend using
[.design-note]
.HTML Notes: Microformats
****
In some cases you may want to include machine-readable structured data in HTML.
https://microformats.org/[Microformats] is one standard for doing so.
It uses classes to mark certain elements as containing information to be extracted.
The microformats2 standard uses five kinds of classes:
* `h-` classes denote that an element represents a machine-readable entity, e.g., `h-entry`, `h-resume`
* The other prefixes denote that an element represents properties of an enclosing entity:
** `p-` classes are plain text properties, from an element's inner text or `alt` attribute, e.g., `p-name`, `p-category`
** `u-` classes are URL properties, from an element's `href` or `src`, e.g., `u-url`, `u-email`, `u-photo`
** `dt-` classes are date/time properties, from `<time>` elements, e.g., `dt-published`, `dt-updated`
** `e-` classes are embedded markup properties, from an element's inner HTML, e.g., `e-content`, `e-description`
There are also conventions for extracting common properties like name, URL and photo without needing classes for each property.
By adding these classes into the HTML representation of an object, we allow the properties of the object to be recovered from the HTML. For example, this simple HTML:
[source,html]
----
<a class="h-card" href="https://john.example">
<img src="john.jpg" alt=""> John Doe
</a>
----
can be parsed into this JSON-like structure:
// maybe: briefly describe how
[source,json]
----
{
"type": ["h-card"],
"properties": {
"name": ["John Doe"],
"photo": ["john.jpg"],
""
"url": ["https://john.example"]
}
}
----
Using a variety of properties and nested objects, we could mark up every bit of information about a contact, for example, in a machine-readable way.
// maybe: rule of thumb for when to use a microformat
// Would this be worthwhile for contact.app?
Microformats and the extensibility of HTML can prove useful, but embedding data in HTML is not appropriate for every use case.
Your human-facing and machine-facing interfaces may end up being limited by each other.
It's often the best option to define a JSON data API separate from your HTML.
****

View File

@ -1,15 +1,13 @@
= Hyperview: A Mobile Hypermedia
:chapter: 12
:chapter: 11
:part: Bringing Hypermedia To Mobile
:part_url: ./part/hyperview/
:url: ./hyperview-a-mobile-hypermedia/
// TODO: using the TODO flag just to revisit edits, adds
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 makes it easy to forget that hypermedia is a general concept, and can be applied to all types of platforms and applications.
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.
@ -25,7 +23,6 @@ I'm using the word "`native`" to refer to code written against an SDK provided b
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.
@ -40,10 +37,10 @@ Since the developer needs to write code to package into an executable, it seems
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.
- 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 SPAs on the web, this architecture has a big downside: the app's logic gets spread across the client and server.
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.
@ -154,16 +151,16 @@ These additions maintain the declarative nature of HTML, while giving developers
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.`"
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.
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.
The developer only has to build the backend to serve Hypermedia responses.
.One HTML server, multiple HTML clients
image::diagram/one-server-many-clients.svg[Many clients connect to one server]
@ -174,7 +171,7 @@ 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?
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.
.One HXML server, one HXML client
@ -213,42 +210,32 @@ HXML responses remain pure, with UI and interactions represented in declarative
=== Which Hypermedia Architecture Should You Use?
We've discussed two approaches for creating mobile apps using hypermedia architecture:
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.
- 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.
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?
So which approach should you use for a Hypermedia-driven mobile app?
Based on our 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.
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.
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.
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 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.
////
TODO: consider adding a brief discussion here or at chapter end
Advice to a developer deciding how to bring an app to mobile
i.e., beyond hypermedia on mobile proof of concept, beyond web vs hyper
include info on maturity of platform, dependencies, any issues getting
hyperview apps past app store guidelines, etc.
Roughly how hyperview apps compare in terms of feel & performance
with native apps.
////
== Introduction to HXML
=== Hello World!
@ -685,7 +672,7 @@ htmx (and Intercooler before it) do exactly that with a new set of attributes:
- 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.
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`".
@ -1252,7 +1239,9 @@ It's not unusual for a production Hyperview app to contain several behaviors, so
Using multiple behaviors with custom actions keeps HXML declarative, without sacrificing functionality.
// TODO: keep? reformat?
.HXML and Hyperview in Brief
We're covering a lot of new material, so we'll add brief summaries to our sprint through Hyperview and HXML.
.Summary
****
- HXML looks similar to HTML, but it uses elements that correspond to mobile UIs, like `<screen>`, `<header>`, `<list>` and more.
- HXML includes input elements that implement common patterns in mobile apps, such as `<switch>`, `<select-single>`, and `<select-multiple>`.
@ -1271,4 +1260,39 @@ There is a story for Hypermedia-Driven Applications on mobile. Mobile app platfo
Hyperview, based on a new format called HXML, offers a path here. It provides an open-source mobile thin-client to render HXML. And HXML opens a took-kit of elements and patterns that correspond to mobile UIs. Developers can evolve Hyperview to suit their apps' requirements, while fully embracing the hypermedia architecture. That's a win.
Yes, hypermedia works for mobile, too. In the next two chapters we'll show how by moving Contact.app to mobile.
Yes, hypermedia can work for mobile apps, too. In the next two chapters we'll show how by moving Contact.app to mobile.
[.design-note]
.HTML Notes: Embedding HTML in Markdown
****
Don't shy away from using embedded HTML in Markdown.
For larger sites, also consider Markdown extensions.
[source,markdown]
----
{.ieee-reference-list} <1>
1. C.H. Gross, A. Stepinski, and D. Akşimşek, <2>
<cite>Hypermedia Systems</cite>, <3>
Bozeman, MT, USA: Big Sky Software.
Available: <https://hypermedia.systems/>
----
<1> Many Markdown dialects let us add ids, classes and attributes using curly braces.
<2> We can now use the <ol> element, and create the brackets in CSS.
<3> We use `<cite>` to mark the title of the work being cited (not the whole citation!)
You can also use custom processors to produce extra-detailed HTML instead of writing it by hand:
[source,markdown]
----
{% reference_list %} <1>
[hypers2023]: <2>
C.H. Gross, A. Stepinski, and D. Akşimşek, _Hypermedia Systems_,
Bozeman, MT, USA: Big Sky Software, 2023.
Available: <https://hypermedia.systems/>
{% end %}
----
<1> `reference_list` is a macro that will transform the plain text to highly-detailed HTML.
<2> A processor can also resolve identifiers, so we don't have to manually keep the reference list in order and the in-text citations in sync.
****

View File

@ -1,13 +1,13 @@
= Building a Contacts App With Hyperview
:chapter: 13
:chapter: 12
:url: ./building-a-contacts-app-with-hyperview/
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 12 argued that hypermedia concepts can and should be applied to platforms other than the Web.
Then, Chapter 11 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.
@ -75,7 +75,7 @@ With our Hyperview client now pointing to the right endpoint, we see a different
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 with our mobile app:
Specifically, let's support the following features in our mobile app:
- A searchable list of contacts
- Viewing the details of a contact
@ -91,7 +91,7 @@ Think of the entrypoint URL as the address you type into a web browser to open a
Except in Hyperview, there is no address bar, and the browser is hard-coded to only open one URL.
This URL will load the first screen when a user launches the app.
Every other action the user can take will be declared in the HXML of that first screen.
This minimal configuration is one elegant aspect of the Hypermedia-driven architecture!
This minimal configuration is one of the benefits of the Hypermedia-driven architecture.
Of course, you may want to write more on-device code to support more features in your mobile app.
We will demonstrate how to do that later in this chapter, in the section called "`Extending the Client.`"
@ -110,7 +110,7 @@ Additionally, we will add a "`pull-to-refresh`" interaction on the list, since u
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.
This base template will contain the style rules of our UI, and a basic structure common to all screens.
Let's call it `layout.xml`.
.Base template `hv/layout.xml`
@ -133,10 +133,10 @@ Let's call it `layout.xml`.
</screen>
</doc>
----
<1> The header section of the template, with a default title
<1> The header section of the template, with a default title.
<2> The content section of the template, to be provided by other templates.
We covered the HXML tags and attributes in the previous chapter.
We're using the HXML tags and attributes covered 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.
@ -188,8 +188,8 @@ It also allows the server to respond with just the `rows.xml` template for inter
{% endfor %}
</items>
----
<1> An HXML element that groups a set of `<item>` elements in a common parent
<2> Iterate over the contacts that were passed in to the template
<1> An HXML element that groups a set of `<item>` elements in a common parent.
<2> Iterate over the contacts that were passed in to the template.
<3> Render an `<item>` for each contact, showing the name, phone number, or email.
In the web app, each row in the list showed the contact's name, phone number, and email address.
@ -249,8 +249,8 @@ 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> Render the appropriate HXML template based on `rows_only`
<1> Check for a new `rows_only` query param.
<2> Render the appropriate HXML template based on `rows_only`.
There's one more change we have to make.
Flask assumes that most views will respond with HTML.
@ -270,7 +270,7 @@ def render_to_response(template_name, *args, **kwargs):
return response
----
<1> Renders the given template with the supplied arguments and keyword arguments.
<2> Create an explicit response object with the rendered template
<2> Create an explicit response object with the rendered template.
<3> Sets the response `Content-Type` header to XML.
As you can see, this helper function uses `render_template()` under the hood.
@ -316,7 +316,7 @@ This requires expanding `<text-field>` to add a `<behavior>` element.
/>
</text-field>
----
<1> This behavior will trigger when the value of the text field changes
<1> This behavior will trigger when the value of the text field changes.
<2> When the behavior triggers, the action will replace the content inside the target element.
<3> The target of the action is the element with ID `contacts-list`.
<4> The replacement content will be fetched from this URL path.
@ -324,16 +324,17 @@ This requires expanding `<text-field>` to add a `<behavior>` element.
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).
with new content (`<item>` elements that match the search query) received in the relative href response. The domain here 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.
.Searching for Contacts
@ -350,11 +351,11 @@ So the value of the `<text-field>` will be included in our HTTP request for the
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`".
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`".
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.
@ -394,23 +395,25 @@ To do this, let's edit `rows.xml` to add a new `<item>` below the Jinja for-loop
{% endif %}
</items>
----
<1> Include an extra `<item>` in the list to show the spinner
<2> The item behavior triggers when visible in the viewport
<3> When triggered, the behavior will replace an element on the screen
<4> The element to be replaced is the item itself (ID `load-more`)
<5> Replace the item with the next page of content
<6> The spinner element
<1> Include an extra `<item>` in the list to show the spinner.
<2> The item behavior triggers when visible in the viewport.
<3> When triggered, the behavior will replace an element on the screen.
<4> The element to be replaced is the item itself (ID `load-more`).
<5> Replace the item with the next page of content.
<6> The spinner element.
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.
@ -460,6 +463,7 @@ Let's add pull-to-refresh to our list of contacts to see it in action.
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.
@ -468,7 +472,7 @@ 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.
Generally, we prefer to use explicit `<behavior>` elements in 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.
@ -511,7 +515,7 @@ def contacts_view(contact_id=0):
----
<1> Generate an XML response from a new template file.
Just like with the `contacts()` view, `contacts_view()` uses `render_to_response()` to set the `Content-Type` header on the response.
Just like 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`
@ -542,20 +546,21 @@ We're also generating the response from a new HXML template, which we can create
</view>
{% endblock %}
----
<1> Extend the base layout template
<2> Override the `header` block of the layout template to include a "Back" button
<3> Behavior to navigate to the previous screen when pressed
<1> Extend the base layout template.
<2> Override the `header` block of the layout template to include a "Back" button.
<3> Behavior to navigate to the previous screen when pressed.
<4> Override the `content` block to show the full details of the selected contact.
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!
We don't need to specify anything extra in the HXML to get these interactions.
.Contact Details Screen
image::screenshot_hyperview_detail_cropped.png[Contact detail shows phone number and email as well as back and edit buttons in navbar]
@ -596,12 +601,12 @@ This button can be added to the header of the contact details screen.
</text>
{% endblock %}
----
<1> The new "`Edit`" button
<2> Behavior to reload the current screen with the edit screen when pressed
<1> The new "`Edit`" button.
<2> Behavior to reload the current screen with the edit screen when pressed.
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.
We'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`
@ -635,10 +640,10 @@ Instead, let's focus on the template for the edit screen.
</form>
{% endblock %}
----
<1> Form wrapping the input fields and buttons
<2> Container with ID, containing the input fields
<3> Template include to render the input fields
<4> Button to submit the form data and update the input fields container
<1> Form wrapping the input fields and buttons.
<2> Container with ID, containing the input fields.
<3> Template include to render the input fields.
<4> Button to submit the form data and update the input fields container.
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.
@ -662,11 +667,11 @@ The actual form fields are defined in a separate template (`form_fields.xml`) an
<!-- same markup for contact.email and contact.phone -->
</view>
----
<1> Text input holding the current value for the contact's first name
<2> Text element that could display errors from the contact model
<3> Another text field, this time for the contact's last name
<1> Text input holding the current value for the contact's first name.
<2> Text element that could display errors from the contact model.
<3> Another text field, this time for the contact's last name.
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.
We 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:
@ -683,7 +688,7 @@ The HXML response will replace the contents of form field container (ID `form-fi
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.
1. If the data is invalid (e.g., 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. 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).
@ -736,19 +741,20 @@ We will update the template to use this flag to implement our desired UI: switch
</view>
----
<1> Only include this behavior after successfully saving a contact.
<2> Trigger the behavior immediately
<3> The behavior will reload the entire screen
<2> Trigger the behavior immediately.
<3> The behavior will reload the entire screen.
<4> The screen will be reloaded with the contact details screen.
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.
.Why not use a redirect?
.Why Not Redirect?
****
You may remember the web app version of this code behaved a little differently.
On a successful save, the view returned `redirect("/contacts/" + str(contact_id))`.
@ -762,6 +768,7 @@ Mobile apps use a nested hierarchy of navigation stacks, modals, and tabs.
All screens in this hierarchy are active, and may be displayed instantly in response to user actions.
In this world, how would the Hyperview client interpret an HTTP redirect?
Should it reload the current screen, push a new one, or navigate to a screen in the stack with the same URL?
Instead of making a choice that would be suboptimal for many scenarios, Hyperview takes a different approach.
Server-controlled redirects are not possible, but the backend can render navigation behaviors into the HXML.
This is what we do to switch from the Edit UI to the Details UI in the code above.
@ -796,12 +803,12 @@ 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> When pressed, reload the entire screen
<1> A new Cancel button on the edit screen.
<2> When pressed, reload the entire screen.
<3> The screen will be reloaded with the contact details screen.
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`".
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.
@ -819,16 +826,17 @@ Let's walk through this series of interactions:
1. Launch the app to the Contacts List.
2. Press on the contact "`Joe Blow`" to load his Contact Details.
3. Press Edit to switch to the edit mode, and change the contact's first name to "`Joseph`".
4. Press Save to switch back to viewing mode. The contact's name is now "`Joseph Blow`".
3. Press Edit to switch to the edit mode, and change the contact's first name to "`Joseph.`"
4. Press Save to switch back to viewing mode. The contact's name is now "`Joseph Blow.`"
5. Hit the back button to return to the Contacts List.
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 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.
@ -836,9 +844,12 @@ Events are built into the behavior system, and allow lightweight communication b
.Event Behaviors
****
Events are a client-side feature of Hyperview.
You are probably familiar with events from working with HTML and the DOM.
Earlier chapter discussed events while working with HTML, htmx and the DOM.
DOM Elements will dispatch events as a result of user interactions.
Scripts can listen for these events, and respond to them by running arbitrary JavaScript code.
We saw some examples in chapter 9.
// TODO: optional, add link to an example
Events in Hyperview are a good deal simpler, but they don't require any scripting and can be defined declaratively in the HXML.
This is done through the behavior system.
Events require adding a new behavior attribute, action type, and trigger type:
@ -870,7 +881,7 @@ The second behavior (on Screen B) will trigger on an event with this name, and t
Events have plenty of uses, but the most common is to inform different screens about backend state changes that require the UI to be re-fetched.
****
We know enough about Hyperview's event system to solve the bug in our app.
We now 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:
@ -891,9 +902,9 @@ Since the `form_fields.xml` template already gets the `saved` flag when the back
/>
{% endif %}
----
<1> Trigger the behavior immediately
<2> The behavior will dispatch an event
<3> The event name is "contact-updated"
<1> Trigger the behavior immediately.
<2> The behavior will dispatch an event.
<3> The event name is "contact-updated".
<4> The existing behavior to show the Details UI.
Now, we just need the contacts list to listen for the `contact-updated` event, and reload itself:
@ -916,16 +927,16 @@ Now, we just need the contacts list to listen for the `contact-updated` event, a
</list>
</form>
----
<1> Trigger the behavior on event dispatch
<2> Trigger the behavior for dispatched events with the name "`contact-updated`"
<3> When triggered, replace the contents of the `<list>` element with rows from the backend
<1> Trigger the behavior on event dispatch.
<2> Trigger the behavior for dispatched events with the name "`contact-updated`".
<3> When triggered, replace the contents of the `<list>` element with rows from the backend.
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 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.
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).
@ -935,6 +946,7 @@ 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.
@ -966,9 +978,9 @@ 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> When pressed, append HXML to a container on the screen
<3> The HXML will be fetched by making a `POST /contacts/<contact_id>/delete` request
<1> New Delete Contact button on the edit screen.
<2> When pressed, append HXML to a container on the screen.
<3> The HXML will be fetched by making a `POST /contacts/<contact_id>/delete` request.
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.
@ -990,20 +1002,21 @@ Therefore, using the `append` action will preserve the current UI while Hypervie
<behavior trigger="load" action="back" /> <2>
</view>
----
<1> On load, dispatch the `contact-updated` event to update the contact lists screen
<1> On load, dispatch the `contact-updated` event to update the contact lists screen.
<2> Navigate back to the contacts list screen.
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.
Once again, we'll 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"`.
@ -1034,8 +1047,8 @@ All we have to do is wrap the delete `<behavior>` in a behavior that uses `actio
</view>
----
<1> Pressing "Delete" triggers an action to show the system dialog with the given title and message.
<2> The first pressable option in the system dialog
<3> Pressing the first option will trigger contact deletion
<2> The first pressable option in the system dialog.
<3> Pressing the first option will trigger contact deletion.
<4> The second pressable option has no behavior, so it only closes the dialog.
Unlike before, pressing the delete button will not have an immediate effect.
@ -1053,13 +1066,12 @@ We can reuse the concepts (and even some templates) from features we've already
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
- 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:
Since the functionality is so similar, we'll summarize the changes here without showing the code.
1. Update `index.xml`.
- Override the `header` block to add a new "`Add`" button.
@ -1082,7 +1094,7 @@ The screen will reload itself to show the Contact Details.
image::screenshot_hyperview_add.png[Form to enter contact details pops up from bottom of screen]
== Deploying the App
With the completion of the contact creation UI, we have a fully implemented mobile 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?
@ -1096,7 +1108,7 @@ This might look something like `flask run --host 192.168.7.229`, where the host
3. 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.
4. 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.
Once you scan the QR code, the full app will run on the device!
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.
@ -1104,6 +1116,7 @@ Any time you make a change in the HXML, just reload the screen to see the UI upd
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:
// TODO: mention presentation here?
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.
@ -1145,11 +1158,11 @@ They will see the Google home page localized in German and Japanese, respectivel
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`".
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`".
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.
@ -1166,7 +1179,8 @@ Content negotiation over content type is done using the `Accept` request 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.
There are two main approaches to content negotiation: 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.
@ -1201,10 +1215,10 @@ def render_to_response(html_template_name, hxml_template_name, *args, **kwargs):
response.headers['Content-Type'] = response_type <4>
return response
----
<1> Function signature takes two templates, one for HTML and one for HXML
<2> Determine whether the client wants HTML or HXML
<3> Select the template based on the best match for the client
<4> Set the `Content-Type` header based on the best match for the client
<1> Function signature takes two templates, one for HTML and one for HXML.
<2> Determine whether the client wants HTML or HXML.
<3> Select the template based on the best match for the client.
<4> Set the `Content-Type` header based on the best match for the client.
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.
@ -1225,7 +1239,8 @@ def contacts_view(contact_id=0):
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.
This meant the Flask views could be kept 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.
@ -1263,9 +1278,9 @@ def index():
else:
return redirect("/web/contacts") <3>
----
<1> Determine whether the client wants HTML or HXML
<2> If the client wants HXML, redirect them to `/mobile/contacts`
<3> If the client wants HTML, redirect them to `/web/contacts`
<1> Determine whether the client wants HTML or HXML.
<2> If the client wants HXML, redirect them to `/mobile/contacts`.
<3> If the client wants HTML, redirect them to `/web/contacts`.
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.
@ -1294,6 +1309,7 @@ After all, we need to write double the number of views: one set for the web appl
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.
@ -1301,12 +1317,39 @@ The dynamic archive UI is a power feature that wouldn't make sense on a mobile d
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.
== Summary
- Creating a Hyperview-powered mobile app is as simple as cloning a Git repo and configuring a single entrypoint URL.
.Summary
****
- It is possible to create a Hyperview-powered mobile app by simply 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.
****
We've covered a lot of ground in this chapter. Take a breath and take stock of how far we've come: we ported the core functionality of the Contact.app web application to mobile. And we did it by re-using much of our Flask backend and while sticking with Jinja templating. We again saw the utility of events for connecting different aspects of an application.
We're not done yet. In the next chapter we'll implement custom behaviors and UI elements to finish our mobile Contact.app.
// TODO: okay, or better as integrated info gem?
[.design-note]
.HTML Notes: Remember <details>
****
Does the information need to be presented as tabs?
Sometimes the answer is yes, but if not, a sequence of details and disclosures fulfills a very similar purpose.
[source,html]
----
<details><summary>Disclosure 1</summary>
Disclosure 1 contents
</details>
<details><summary>Disclosure 2</summary>
Disclosure 2 contents
</details>
----
Compromising UX just to avoid JavaScript is bad development.
But sometimes it's possible to achieve an equal (or better!) quality of UX while allowing for a simpler and more robust implementation.
****

View File

@ -1,20 +1,21 @@
= Extending the Hyperview Client
:chapter: 14
:chapter: 13
:url: ./extending-the-hypermedia-client/
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.
As app developers, we want 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.
Before diving in, let's take a quick look at the tech stack we'll be using.
The Hyperview client is written in React Native, 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.
@ -59,20 +60,20 @@ 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> When pressed, prompt the user to call the given phone number
<1> Define an alias for an XML namespace used by our new attributes.
<2> When pressed, prompt the user to call the given phone number.
<3> When pressed, open an e-mail client with the given address populated in the `to:` field.
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.
We chose to use attributes, but we could have represented the phone number or email using child elements.
We're 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.
First, we're 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.
[source,bash]
@ -104,7 +105,7 @@ export default {
};
----
<1> Import the function we need from the 3rd party library.
<2> The name of the action
<2> The name of the action.
<3> The callback that runs when the action triggers.
<4> Get the phone number from the `<behavior>` element.
<5> Pass the phone number to the function from the 3rd party library.
@ -117,7 +118,7 @@ That means we can call methods on it like `getAttribute`, or access attributes l
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.
There's one more thing 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.
@ -145,12 +146,13 @@ export default class HyperviewScreen extends PureComponent {
}
}
----
<1> Import the open-phone action
<2> Create an array of custom actions
<1> Import the open-phone action.
<2> Create an array of custom actions.
<3> Pass the custom actions to the `Hyperview` component, as a prop called `behaviors`.
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:
@ -185,32 +187,32 @@ In fact, let's do that now by updating the `show.xml` template in our Flask app:
</view>
{% endblock %}
----
<1> Add a behavior to the phone number section that triggers on "`press`".
<1> Add a behavior to the phone number section that triggers on "`press.`"
<2> Trigger the new "`open-phone`" action.
<3> Set the attribute expected by the "`open-phone`" action.
<4> Same idea, with a different action ("`open-email`")
<4> Same idea, with a different action ("`open-email`").
I skipped over the implementation of the second custom action, "`open-email`".
We'll skip 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 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 now have a complete example of extending the client with 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.
== Adding Toast Messages
The phone and email actions added in the previous section are examples of "`system actions`".
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.
But custom actions are not limited to 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.
Then the base `layout.html` template renders the messages into the final web page.
.Snippet templates/layout.html
----
@ -221,6 +223,7 @@ Then the base `layout.html` template rendered the messages into the final web pa
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.
@ -258,18 +261,18 @@ export default {
};
----
<1> Import the `Toast` API.
<2> The name of the action
<3> The callback that runs when the action triggers
<4> Pass the message to the toast library
<2> The name of the action.
<3> The callback that runs when the action triggers.
<4> Pass the message to the toast library.
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.
For simplicity, we're 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.
Now we need to register our custom action with the `<Hyperview>` component, by passing it to the `behaviors` prop.
.demo/src/HyperviewScreen.js
[source,js]
@ -316,8 +319,8 @@ Since the actual behaviors will be the same in both templates, let's create a sh
/>
{% endfor %}
----
<1> Define a behavior for each message to display
<2> Trigger this behavior as soon as the screen loads
<1> Define a behavior for each message to display.
<2> Trigger this behavior as soon as the screen loads.
<3> Trigger the new "`show-toast`" action.
<4> The "`show-toast`" action will display the flashed message in its UI.
@ -353,7 +356,7 @@ And we can do the same thing in `deleted.xml`:
----
<1> Show the toasts as soon as the screen loads.
In both `form_fields.xml` and `deleted.xml`, multiple behaviors get triggered on "`load`".
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.
@ -390,16 +393,17 @@ Rather than implementing the swipe gesture from scratch, we will once again use
> yarn add react-native-swipeable <1>
> yarn start <2>
----
<1> Add dependency on `react-native-swipeable`
<2> Re-start the mobile app
<1> Add dependency on `react-native-swipeable`.
<2> Re-start the mobile app.
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:
[source,xml]
----
@ -417,18 +421,19 @@ I came up with something that looks like this:
</swipe:button>
</swipe:row>
----
<1> Parent element encapsulating the entire swipeable row, with custom namespace
<2> The main content of the swipeable row, can hold any HXML
<3> The first button that appears when swiping, can hold any HXML
<4> The second button that appears when swiping, can hold any HXML
<1> Parent element encapsulating the entire swipeable row, with custom namespace.
<2> The main content of the swipeable row, can hold any HXML.
<3> The first button that appears when swiping, can hold any HXML.
<4> The second button that appears when swiping, can hold any HXML.
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.
The previous markup contains nothing specific to the contact name, editing the contact, or deleting the contact.
If later 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.
@ -438,13 +443,13 @@ 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
- `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.
That means it needs to delegate 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.
@ -486,13 +491,13 @@ export default class SwipeableRow extends PureComponent { <1>
}
}
----
<1> Class-based React Native component
<2> Map this component to the given HXML namespace
<3> Map this component to the given HXML tag name
<1> Class-based React Native component.
<2> Map this component to the given HXML namespace.
<3> Map this component to the given HXML tag name.
<4> Function that returns an array of React Native components for each `<button>` element.
<5> Delegate to the Hyperview client to render each button
<6> Pass the buttons and main content to the third-party library
<7> Delegate to the Hyperview client to render the main content
<5> Delegate to the Hyperview client to render each button.
<6> Pass the buttons and main content to the third-party library.
<7> Delegate to the Hyperview client to render the main content.
The `SwipeableRow` class implements a React Native component.
At the top of the class, we set a static `namespaceURI` property and `localName` property.
@ -500,23 +505,23 @@ These properties map the React Native component to a unique namespace and tag na
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.
Since React Native is built on the 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()`
- 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`.
.Component rendering delegation
image::figure_hyperview_components.png["The HyperviewClient delegates to us to render XML swipe:row element. We delegate back to render text elements."]
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).
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 result: 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.
@ -553,11 +558,11 @@ export default class HyperviewScreen extends PureComponent {
}
}
----
<1> Import the `SwipeableRow` component
<2> Create an array of custom components
<1> Import the `SwipeableRow` component.
<2> Create an array of custom components.
<3> Pass the custom component to the `Hyperview` component, as a prop called `components`.
We're now ready to update our HXML templates to make use of the new swipeable row component!
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:
@ -627,8 +632,8 @@ Let's add two buttons to the swipeable row.
</swipe:row>
</item>
----
<1> Added `<swipe:button>` for edit action
<2> Added `<swipe:button>` for delete action
<1> Added `<swipe:button>` for edit action.
<2> Added `<swipe:button>` for delete action.
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.
@ -682,10 +687,10 @@ This interaction will work for our action button as well:
</view>
</swipe:button>
----
<1> When pressed, open a system dialog box asking the user to confirm the action
<1> When pressed, open a system dialog box asking the user to confirm the action.
<2> If confirmed, make a POST request to the deletion endpoint, and append the response to the parent `<item>`.
Now when we press "`Delete`", we get the confirmation dialog as expected.
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.
@ -697,22 +702,61 @@ If we wanted to, we could have the action buttons trigger our custom actions, li
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.
In fact, we'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.
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.
== Conclusion
.Summary
****
- 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.
****
//TODO: conclusion okay? add?
=== Mobile Hypermedia-Driven Applications
That concludes our build of mobile Contact.app. Step back from the code details and consider the broader pattern: the Hypermedia-Driven Application architecture allowed for signicant code reuse, and led to a manageable stack for ongoing app updates and maintenance for both web and mobile.
Yes, there is a story for Hypermedia-Driven Applications on mobile.
[.design-note]
.HTML Note: "'Display: none'"
****
Sadly, not even good HTML-- or HXML -- can cure all ills.
If you care about machine readability, or human readability, or page weight, the most important thing to do is _testing_.
Test manually.
Test automatically.
Test with screenreaders, test with a keyboard, test on different browsers and hardware, and run linters (while coding and/or in CI).
// TODO: okay here? not sure where to put this
One common problem is with the use of `display: none;` in CSS. The issue is that it is not purely cosmetic -- it also removes elements from the accessibility tree and keyboard focus. If you want to hide an element visually without hiding it from assistive technology, you can use this utility class:
[source,css]
----
.vh {
clip: rect(0 0 0 0);
clip-path: inset(50%);
block-size: 1px;
inline-size: 1px;
overflow: hidden;
white-space: nowrap;
}
----
`vh` is short for "`visually hidden.`" This class uses multiple methods and workarounds to make sure no browser removes the element's function.
****

View File

@ -1,6 +1,6 @@
= Conclusion
:chapter: 15
:chapter: 14
:part: Conclusion
:part_url: ./part/conclusion/
:url: ./conclusion/
@ -13,7 +13,7 @@ or a technology only appropriate for "`documents`" of links, text and pictures,
building _applications_. In this book you have seen how to build sophisticated user interfaces -- for both the web, with htmx,
and for mobile applications, using Hyperview -- using hypermedia as a core underlying application technology.
// TODO: check edits
// check: edits
Many web developers 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, there are now JavaScript libraries that extend HTML by addressing its core limitations. Htmx, for example, allowed us to:
@ -50,12 +50,12 @@ technology is going to change everything. It tends to favor _sophistication_ ov
_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.`"
// TODO: check, emphasized established, vs. old or past
// check: emphasize established, vs. old or past
The software industry tends, especially in web development, to lean far more towards innovating, rather than
understanding existing technologies and building on them or within them. We tend to look ahead for new, genius
solutions, rather than looking to established ideas. This is understandable: the technology world is necessarily
a forward-looking industry.
// TODO: check, refer back to Fielding
// check: refer back to Fielding?
On the other hand -- as we saw with Roy Fielding's formulation of REST -- some early architects of the web had some great ideas which have been overlooked. 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 powerful ideas like REST discarded so cavalierly
@ -64,5 +64,41 @@ by the industry. Fortunately, the concepts are still sitting there, waiting to b
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.
// TODO: suggest this as the stronger concluding line
// check: suggest this as the stronger concluding line
Perhaps it's time to give hypermedia a chance.
// TODO: check. Does this list accurately condense the work?
[.design-note]
.HTML Notes: Toward Effective HTML
****
Throughout the book we discuss a number of best practices for writing effective HTML. They are, in a nutshell:
* Stay close to the HTML markup youre producing and be able to change it. Prefer frameworks and components that help you do this.
// TODO: maybe add. Aim for 'locality of behavior' in your markup, so that you or another developer can understand what each part of the page is doing just by looking at that part of the page.
* Make the full range of available tags part of your toolkit.
* Aim for good fit between tag specs and your use case; check the HTML specs when in doubt.
* When more specific tags dont fit, it is usually better to step back to <div> than to use a tag incorrectly.
* Stick with established libraries for UI interactions. If a use case requires an improvised solution, test carefully for keyboard interaction and accessibility.
* Prefer components that extend HTML, rather than abstracting it away.
* Test your HTML with screenreaders, with a keyboard, with different browsers and hardware, and run linters (while coding and/or in CI).
// TODO: Finish or cut. Placement okay?
.Helpful Resources
* Foundations
* HTML specification: https://html.spec.whatwg.org/multipage
* TODO link resources on alt text.
* https://htmhell.dev: Along with sinister abuses of HTML, this website shares development tips that will help ypu keep up-to-date with best practice.
* Referenced
** Manuel Matuzović, [.cite]_Lost in Translation_, https://www.youtube.com/watch?v=Wno1IhEBTxc.
** Manuel Matuzović, [.cite]_Why I'm not the biggest fan of Single Page Applications_, https://www.matuzo.at/blog/2023/single-page-applications-criticism/
** [.cite]_semantic: the 8 letter s-word_, https://t-ravis.com/post/doc/semantic_the_8_letter_s-word/
****