<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Craft Code</title>
    <description>The latest articles on Forem by Craft Code (@craft-code).</description>
    <link>https://forem.com/craft-code</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F7869%2Ff0df6653-76a0-4f86-82e0-bf0f36c1784b.png</url>
      <title>Forem: Craft Code</title>
      <link>https://forem.com/craft-code</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/craft-code"/>
    <language>en</language>
    <item>
      <title>Data-driven UIs</title>
      <dc:creator>Charles F. Munat</dc:creator>
      <pubDate>Wed, 01 May 2024 01:37:44 +0000</pubDate>
      <link>https://forem.com/craft-code/data-driven-uis-481o</link>
      <guid>https://forem.com/craft-code/data-driven-uis-481o</guid>
      <description>&lt;p&gt;&lt;strong&gt;DDUIs mean a single source of truth and the benefits that come with it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Building on a previous article, &lt;a href="https://dev.to/craft-code/dom-to-json-and-back-3mgc"&gt;DOM to JSON and back&lt;/a&gt;, we now move on to &lt;strong&gt;data-driven UIs.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A data-driven UI is a user interface generated dynamically from a query response. In short, the database &lt;em&gt;schema&lt;/em&gt; determines the interface to that data.&lt;/p&gt;

&lt;p&gt;This gives us a &lt;strong&gt;single source of truth.&lt;/strong&gt; Our interface (e.g., forms) &lt;em&gt;cannot&lt;/em&gt; get out of synch with our data source. If we update the database, then the interface updates itself instantly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaway
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Here is how things are usually done:&lt;/strong&gt; we build a database with a schema.&lt;/p&gt;

&lt;p&gt;Then we build an API that sits in front of it. Then we build an interface to that API. All hard-coded.&lt;/p&gt;

&lt;p&gt;Have fun keeping them in synch.&lt;/p&gt;

&lt;p&gt;What we propose here is to &lt;strong&gt;extend the database schema.&lt;/strong&gt; We add enough information to it to allow us to generate the API and UI dynamically. Then we generate them &lt;em&gt;on the fly.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This means we have &lt;strong&gt;one&lt;/strong&gt; source of truth. When that source changes, the interface updates itself instantly to match it.&lt;/p&gt;

&lt;p&gt;We call this a &lt;strong&gt;data-driven UI.&lt;/strong&gt; It rocks.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  On this page
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Start at the finish&lt;/li&gt;
&lt;li&gt;
Widget by widget

&lt;ol&gt;
&lt;li&gt;ID&lt;/li&gt;
&lt;li&gt;Name&lt;/li&gt;
&lt;li&gt;Email address&lt;/li&gt;
&lt;li&gt;Display name on profile&lt;/li&gt;
&lt;li&gt;The “Save changes” button&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;li&gt;
Putting it all together

&lt;ol&gt;
&lt;li&gt;Adding HATEOAS&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;li&gt;
Oh, the benefits!

&lt;ol&gt;
&lt;li&gt;Whatʼs next?&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id="start-at-the-finish"&gt;Start at the finish&lt;/h2&gt;

&lt;p&gt;To determine what we need to add to our database schema and query response, letʼs start with the &lt;strong&gt;interface.&lt;/strong&gt; Thatʼs where the rubber meets the road, right? Weʼll keep it simple.&lt;/p&gt;

&lt;p&gt;How about a form used to update a userʼs profile? Something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"edit-profile"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"_charset_"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"255c08b2-6606-424b-a339-d3f9ebe50a21"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-field"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Name&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"email-field"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Email address&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"boolean-field"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"display-name-on-profile"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
        &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"display-name-on-profile"&lt;/span&gt;
        &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"displayNameOnProfile"&lt;/span&gt;
        &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"checkbox"&lt;/span&gt;
        &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      Display email on profile
    &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;fieldset&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"member-picker"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;legend&amp;gt;&lt;/span&gt;Favorite color&lt;span class="nt"&gt;&amp;lt;/legend&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"gray"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
        &lt;span class="na"&gt;checked&lt;/span&gt;
        &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"gray"&lt;/span&gt;
        &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"favoriteColor"&lt;/span&gt;
        &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"radio"&lt;/span&gt;
        &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"#999"&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      gray
    &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"red"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
        &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"red"&lt;/span&gt;
        &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"favoriteColor"&lt;/span&gt;
        &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"radio"&lt;/span&gt;
        &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"#f00"&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      red
    &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"green"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
        &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"green"&lt;/span&gt;
        &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"favoriteColor"&lt;/span&gt;
        &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"radio"&lt;/span&gt;
        &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"#0f0"&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      green
    &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"blue"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
        &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"blue"&lt;/span&gt;
        &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"favoriteColor"&lt;/span&gt;
        &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"radio"&lt;/span&gt;
        &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"#00f"&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      blue
    &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/fieldset&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"button-bar"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Save changes&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What did we need to know to code this form?&lt;/p&gt;

&lt;p&gt;First, we need to know what data it will include and the datatype of each:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The userʼs &lt;strong&gt;ID,&lt;/strong&gt; which is of type &lt;code&gt;UUID&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The userʼs &lt;strong&gt;name,&lt;/strong&gt; which is a character &lt;code&gt;string&lt;/code&gt;. This might have some limitations, such as characters allowed or limits on length.&lt;/li&gt;
&lt;li&gt;The userʼs &lt;strong&gt;email address,&lt;/strong&gt; which is of type &lt;code&gt;EmailAddress&lt;/code&gt;. This type does not yet exist, so we will create it.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;boolean&lt;/code&gt; flag to decide whether to display the userʼs email address on their profile or leave it off.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="widget-by-widget"&gt;Widget by widget&lt;/h2&gt;

&lt;p&gt;Now letʼs consider what else we need to know.&lt;/p&gt;

&lt;h3 id="id"&gt;ID&lt;/h3&gt;

&lt;p&gt;We know that the user cannot update their &lt;strong&gt;ID,&lt;/strong&gt; so this is readonly. Should the user see this? It would only add to the clutter. So letʼs make it &lt;code&gt;hidden&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We donʼt care what type the ID is because we are going to return it unchanged. That said, we know that it is a &lt;code&gt;UUID&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;From the above, we can see that the proper widget for the ID is an &lt;code&gt;input&lt;/code&gt; of type &lt;code&gt;hidden&lt;/code&gt;. We can treat the UUID as a string.&lt;/p&gt;

&lt;h3 id="name"&gt;Name&lt;/h3&gt;

&lt;p&gt;We know that the user can update their &lt;strong&gt;name,&lt;/strong&gt; so this field is &lt;code&gt;mutable&lt;/code&gt;. And &lt;code&gt;visible&lt;/code&gt;. And itʼs a &lt;code&gt;string&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A short string, so that means an &lt;code&gt;input&lt;/code&gt; of type &lt;code&gt;text&lt;/code&gt; rather than a &lt;code&gt;textarea&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id="email-address"&gt;Email address&lt;/h3&gt;

&lt;p&gt;We also know that the user can update their &lt;strong&gt;email address&lt;/strong&gt;. And &lt;code&gt;verify&lt;/code&gt; it by clicking on a link in an email sent to that address. It should be &lt;code&gt;mutable&lt;/code&gt; and &lt;code&gt;visible&lt;/code&gt; and of type &lt;code&gt;EmailAddress&lt;/code&gt;, hence an EmailField.&lt;/p&gt;

&lt;p&gt;That gives us a &lt;strong&gt;semantic&lt;/strong&gt; advantage. The browser can do validation for us. Or we can add our own, but use the browserʼs validation as a fall back.&lt;/p&gt;

&lt;p&gt;We also need to set the &lt;strong&gt;name&lt;/strong&gt; and &lt;strong&gt;email address&lt;/strong&gt; to &lt;code&gt;required&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id="display-name-on-profile"&gt;Display name on profile&lt;/h3&gt;

&lt;p&gt;This field requires a simple yes or no. Does the user want to display their email address on their profile?&lt;/p&gt;

&lt;p&gt;The type, then, is &lt;code&gt;boolean&lt;/code&gt;, and the best way to collect this data point is an &lt;code&gt;input&lt;/code&gt; of type &lt;code&gt;checkbox&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If we do this correctly in the database, then the type is a &lt;code&gt;BOOLEAN&lt;/code&gt; type. So the value is either &lt;code&gt;TRUE&lt;/code&gt; or &lt;code&gt;FALSE&lt;/code&gt;. We can build our component so that it works with this type.&lt;/p&gt;

&lt;p&gt;Does it matter that the specific widget is a checkbox? No. So we call this widget a &lt;strong&gt;BooleanField&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;One important distinction of a DDUI is that it is &lt;strong&gt;schema-centric.&lt;/strong&gt; We donʼt care what &lt;em&gt;widget&lt;/em&gt; we use. Thatʼs up to the interface. We care about the &lt;em&gt;datatype&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That is why we call this a &lt;strong&gt;BooleanField&lt;/strong&gt; instead of a “CheckboxField“. It may seem trivial, but precise language is very important. It helps to re-orient oneʼs brain toward a DDUI approach.&lt;/p&gt;

&lt;h3 id="favorite-color"&gt;Favorite color&lt;/h3&gt;

&lt;p&gt;Now we are asking the user to make a choice from a &lt;strong&gt;set&lt;/strong&gt; of options. The key word here is &lt;code&gt;set&lt;/code&gt;. In short, we are asking the user to choose one &lt;code&gt;member&lt;/code&gt; from that set.&lt;/p&gt;

&lt;p&gt;The widget for this is then a &lt;strong&gt;MemberPicker&lt;/strong&gt; because it permits one to choose a single &lt;strong&gt;member&lt;/strong&gt; from a set. If we were allowing multiple selections, then weʼd have a &lt;strong&gt;SubsetPicker&lt;/strong&gt; (or Chooser or whatever).&lt;/p&gt;

&lt;p&gt;Items in a set are also called &lt;em&gt;elements.&lt;/em&gt; But “ElementPicker“ might be confusing as we are also working with HTML elements.&lt;/p&gt;

&lt;p&gt;In this instance, our set has four members, each with a label and a value:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;gray: &lt;code&gt;#999&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;red: &lt;code&gt;#f00&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;green: &lt;code&gt;#0f0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;blue: &lt;code&gt;#00f&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Besides the &lt;code&gt;favoriteColor&lt;/code&gt;, we will need this full set of color options. The back end will have to provide these, but theyʼd be in the HTML anyway.&lt;/p&gt;

&lt;p&gt;What widget should we choose? Well, we have options. We could use a &lt;code&gt;select&lt;/code&gt; element. But for only four choices, it would be nice to see all at once. That means inputs of type &lt;code&gt;radio&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But see below.&lt;/p&gt;

&lt;h3 id="the-save-changes-button"&gt;The “Save changes” button&lt;/h3&gt;

&lt;p&gt;We need some way to trigger the &lt;strong&gt;action&lt;/strong&gt; that saves our changes. There are several possibilities, but easiest is to wrap our inputs in a &lt;code&gt;form&lt;/code&gt;. And add a submit &lt;code&gt;button&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And here comes &lt;a href="https://en.wikipedia.org/wiki/Roy_Fielding"&gt;Roy Fielding's&lt;/a&gt; “Hypertext As The Engine Of Application State”: HATEAOS. Our server needs to tell us which actions are available for Profile.&lt;/p&gt;

&lt;p&gt;When we load our example profile page, the user agent (browser) makes an HTTP request to the server. The server sends an HTML document. This links to various other documents: images, stylesheets, scripts, etc.&lt;/p&gt;

&lt;p&gt;In our scenario, this page then does an AJAX request to retrieve the data.&lt;/p&gt;

&lt;p&gt;On most pages, the HTML form is already in place. We use JavaScript to enter into the form using JavaScript. But in our case, with a DDUI app, the form does not exist until the AJAX requests returns. &lt;em&gt;Then&lt;/em&gt; JavaScript both builds the form and fills it with the data.&lt;/p&gt;

&lt;p&gt;As the form is not hard coded, we donʼt know where to submit it. What is the formʼs &lt;code&gt;action&lt;/code&gt;? Is it a &lt;code&gt;GET&lt;/code&gt; or a &lt;code&gt;POST&lt;/code&gt;? So we need this metadata in our AJAX response.&lt;/p&gt;

&lt;p&gt;This has the added benefit of making the API discoverable.&lt;/p&gt;

&lt;h2 id="putting-it-all-together"&gt;Putting it all together&lt;/h2&gt;

&lt;p&gt;So here is a first pass at our current schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Profile"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"uuid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"readonly"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"hidden"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EmailAddress"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"displayNameOnProfile"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"boolean"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"favoriteColor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Member"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gray"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"options"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gray"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#999"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"red"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#f00"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"green"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#0f0"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"blue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#00f"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Easy peasy, right?&lt;/p&gt;

&lt;p&gt;Our ID is not visible, so we know to use a &lt;code&gt;hidden&lt;/code&gt; field for it. So:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name is a &lt;code&gt;StringField&lt;/code&gt;. We use an input of type &lt;code&gt;text&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Email is an &lt;code&gt;EmailField&lt;/code&gt;. This uses an &lt;code&gt;email&lt;/code&gt; input.&lt;/li&gt;
&lt;li&gt;“displayNameOnProfile” is a &lt;code&gt;BooleanField&lt;/code&gt;: an input of type &lt;code&gt;checkbox&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;And “favoriteColor” is a &lt;code&gt;MemberPicker&lt;/code&gt;. In this instance, a set of inputs of type &lt;code&gt;radio&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We get all this from the &lt;strong&gt;query response&lt;/strong&gt; and the &lt;strong&gt;schema.&lt;/strong&gt; We no longer need to &lt;em&gt;hard code&lt;/em&gt; the form.&lt;/p&gt;

&lt;p&gt;We also know to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Default &lt;code&gt;displayNameOnProfile&lt;/code&gt; to &lt;code&gt;checked&lt;/code&gt; (true)&lt;/li&gt;
&lt;li&gt;Set our default &lt;code&gt;favoriteColor&lt;/code&gt; to &lt;code&gt;gray&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And we know that the whole thing is the &lt;code&gt;Profile&lt;/code&gt; type.&lt;/p&gt;

&lt;p&gt;We could return this schema &lt;em&gt;parallel&lt;/em&gt; to the query response containing our values. But why not insert the current values and return them with our schema?&lt;/p&gt;

&lt;p&gt;Our Profile schema with current values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Profile"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"uuid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"readonly"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"hidden"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"255c08b2-6606-424b-a339-d3f9ebe50a21"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bob Dobbs"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EmailAddress"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bob@dobbs.guru"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"verified"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"displayNameOnProfile"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"boolean"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"favoriteColor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Member"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gray"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"options"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gray"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#999"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"red"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#f00"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"green"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#0f0"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"blue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#00f"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"blue"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OK, now what can we do with this?&lt;/p&gt;

&lt;h3 id="adding-hateoas"&gt;Adding HATEOAS&lt;/h3&gt;

&lt;p&gt;As mentioned above, we need to include a set of available &lt;strong&gt;actions.&lt;/strong&gt; For our Profile, these are &lt;strong&gt;CRUDL:&lt;/strong&gt; create, retrieve, update, delete, and list. Letʼs put them in an &lt;code&gt;actions&lt;/code&gt; property and add them to our &lt;code&gt;Profile&lt;/code&gt; schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"actions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"create"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PUT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/profiles/{id}"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"retrieve"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/profiles/{id}"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"update"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PATCH"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/profiles/{id}"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"delete"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DELETE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/profiles/{id}"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"list"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/profiles"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course, we may not permit all these actions all the time. We have to consider &lt;strong&gt;authorization&lt;/strong&gt; as well. But then we simply leave them off the &lt;code&gt;actions&lt;/code&gt; list.&lt;/p&gt;

&lt;p&gt;There is much more that we can do with this! For example, we can un-camelCase the &lt;code&gt;name&lt;/code&gt; property to get the field label. Better, letʼs default to that, but use a &lt;code&gt;label&lt;/code&gt; property to override the &lt;code&gt;name&lt;/code&gt; where needed.&lt;/p&gt;

&lt;p&gt;We can also add more data to our actions. For example, a label for the button (defaulting to “create”, etc.). Or a description of what the action does. Or limits on the action, such as a &lt;code&gt;Duration&lt;/code&gt; that disables it except during that duration. (Or the reverse.)&lt;/p&gt;

&lt;p&gt;The possibilities are endless.&lt;/p&gt;

&lt;h2 id="oh-the-benefits"&gt;Oh, the benefits!&lt;/h2&gt;

&lt;p&gt;The most important benefit of a DDUI is the one mentioned at the top of this essay: &lt;strong&gt;a single source of truth.&lt;/strong&gt; But there is so much more.&lt;/p&gt;

&lt;p&gt;This approach requires &lt;strong&gt;much less effort.&lt;/strong&gt; Devs donʼt hard code the same set of components over and over again. Instead, they maintain the &lt;code&gt;jsonToDom&lt;/code&gt; mapping. Once we build this renderer, we need only to add occasional new options.&lt;/p&gt;

&lt;p&gt;Your database schema then represents two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your domain&lt;/li&gt;
&lt;li&gt;Metadata to help configure the interface&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your database already knows the datatype of everything you persist. Why not clue in the front end? We can also include &lt;code&gt;metadata&lt;/code&gt; to permit us to configure the front end.&lt;/p&gt;

&lt;p&gt;For example: we used &lt;code&gt;radio&lt;/code&gt; buttons for our &lt;code&gt;MemberPicker&lt;/code&gt;. But what if there are fifty options? Then weʼd likely want a &lt;code&gt;select&lt;/code&gt; element instead. Or some kind of “typeahead” widget. Our &lt;code&gt;MemberPicker&lt;/code&gt; widget will output any of these, but how does it know which one to use?&lt;/p&gt;

&lt;p&gt;One way is to set the type of widget in the query response, but thatʼs a bad idea. The schema should have no knowledge or interest in &lt;em&gt;how&lt;/em&gt; we present these data.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;For one to seven options, use &lt;code&gt;radio&lt;/code&gt; buttons.&lt;/li&gt;
&lt;li&gt;For eight to thirty-two options, use a &lt;code&gt;select&lt;/code&gt; element.&lt;/li&gt;
&lt;li&gt;For thirty-three or more options, use a &lt;strong&gt;lookahead&lt;/strong&gt; field.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We could hard code this into the widget, but why not load a configuration when the app starts up?&lt;/p&gt;

&lt;p&gt;We can also run this on the server side if we prefer. Or generate partial HTML to create a static site and then do the rest on the client side. Kind of how React used to work.&lt;/p&gt;

&lt;p&gt;Now, when we need to change the interface, we donʼt have to create the race condition we usually get. Thatʼs where the back and and front end devs block each other.&lt;/p&gt;

&lt;p&gt;We also donʼt have to make changes in three different places. &lt;em&gt;With the virtual certainty that they will get out of synch and create problems.&lt;/em&gt; Instead, we update the database once and presto! The front end updates itself instantly.&lt;/p&gt;

&lt;p&gt;Here is our final simple DDUI schema for Profile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Profile"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"hidden"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"readonly"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"uuid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"255c08b2-6606-424b-a339-d3f9ebe50a21"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bob Dobbs"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Email address"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EmailAddress"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bob@dobbs.guru"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"verified"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"displayNameOnProfile"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"boolean"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gray"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"favoriteColor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"options"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gray"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#999"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"red"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#f00"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"green"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#0f0"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"blue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#00f"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Member"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"blue"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"actions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"create"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PUT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/profiles/{id}"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"retrieve"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/profiles/{id}"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"update"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PATCH"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/profiles/{id}"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"delete"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DELETE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/profiles/{id}"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"list"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/profiles"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above JSON, when run through our renderer function, will generate the HTML below. This is an &lt;strong&gt;update form.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt;
  &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"/profiles/255c08b2-6606-424b-a339-d3f9ebe50a21"&lt;/span&gt;
  &lt;span class="na"&gt;data-method=&lt;/span&gt;&lt;span class="s"&gt;"PATCH"&lt;/span&gt;
  &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"edit-profile"&lt;/span&gt;
  &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"_charset_"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
    &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt;
    &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;
    &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"255c08b2-6606-424b-a339-d3f9ebe50a21"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-field"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Name&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
      &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;
      &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;
      &lt;span class="na"&gt;required&lt;/span&gt;
      &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;
      &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Bob"&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"email-field"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Email address&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
      &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
      &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
      &lt;span class="na"&gt;required&lt;/span&gt;
      &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
      &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"bob@dobbs.com"&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"boolean-field"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"display-name-on-profile"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
        &lt;span class="na"&gt;checked&lt;/span&gt;
        &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"display-name-on-profile"&lt;/span&gt;
        &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"displayNameOnProfile"&lt;/span&gt;
        &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"checkbox"&lt;/span&gt;
        &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      Display email on profile
    &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;fieldset&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"member-picker"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;legend&amp;gt;&lt;/span&gt;Favorite color&lt;span class="nt"&gt;&amp;lt;/legend&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"gray"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
        &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"gray"&lt;/span&gt;
        &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"favoriteColor"&lt;/span&gt;
        &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"radio"&lt;/span&gt;
        &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"#999"&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      gray
    &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"red"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
        &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"red"&lt;/span&gt;
         &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"favoriteColor"&lt;/span&gt;
        &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"radio"&lt;/span&gt;
        &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"#f00"&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      red
    &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"green"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
        &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"green"&lt;/span&gt;
        &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"favoriteColor"&lt;/span&gt;
        &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"radio"&lt;/span&gt;
        &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"#0f0"&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      green
    &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"blue"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
        &lt;span class="na"&gt;checked&lt;/span&gt;
        &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"blue"&lt;/span&gt;
        &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"favoriteColor"&lt;/span&gt;
        &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"radio"&lt;/span&gt;
        &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"#00f"&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      blue
    &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/fieldset&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"button-bar"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Save changes&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3 id="whats-next"&gt;Whatʼs next?&lt;/h3&gt;

&lt;p&gt;Look for more detailed articles on this topic in the future. With plenty of code examples. Even some npm (or JSR) libraries.&lt;/p&gt;

&lt;p&gt;But the most exciting part of this is the core of the whole system: the database. We need a database that will permit us to provide infinite detail about our schema. And keep the schema strict. And permit us to update it &lt;em&gt;on-the-fly&lt;/em&gt; whenever we want to.&lt;/p&gt;

&lt;p&gt;And we know just the right type of database for this purpose.&lt;/p&gt;

&lt;p&gt;More soon.&lt;/p&gt;

</description>
      <category>ddui</category>
      <category>nocode</category>
      <category>vanilla</category>
      <category>craftcode</category>
    </item>
    <item>
      <title>The proximity principle</title>
      <dc:creator>Charles F. Munat</dc:creator>
      <pubDate>Thu, 29 Feb 2024 23:16:39 +0000</pubDate>
      <link>https://forem.com/craft-code/the-proximity-principle-313i</link>
      <guid>https://forem.com/craft-code/the-proximity-principle-313i</guid>
      <description>&lt;p&gt;&lt;strong&gt;Design principles make for better DX and better code.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;8 minutes or so, 2056 words, 5th grade&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Do you want to write high-performing code quickly, without bugs or tech debt? We hope thatʼs a rhetorical question.&lt;/p&gt;

&lt;p&gt;If your answer is yes, then here is the trick: &lt;strong&gt;reduce cognitive load.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The more you do to keep cognitive load to a minimum, the better your code will be. The greater the cognitive load, the harder it will be to understand and reason about your code.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Key takeaway&lt;/p&gt;

&lt;p&gt;There are plenty of arguments about the right way to code web applications.&lt;/p&gt;

&lt;p&gt;Some argue for strict “separation of concerns”. Others want to work with a single language. They prefer to generate their HTML and CSS programatically.&lt;/p&gt;

&lt;p&gt;We beg to differ. We insist that the key to great code is to &lt;a href="https://craft-code.dev/methods/minimize-cognitive-footprint" rel="noopener noreferrer"&gt;minimize cognitive footprint.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One way to do this is to apply the proximity principle from design thinking to our code. When we do so, it becomes clear how separating HTML, CSS, and JavaScript works to reduce cognitive load.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  On this page
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The proximity principle explained&lt;/li&gt;
&lt;li&gt;Applying the proximity principle to code&lt;/li&gt;
&lt;li&gt;
Forget separation of concerns

&lt;ul&gt;
&lt;li&gt;
HyperText Markup Language (HTML)&lt;/li&gt;
&lt;li&gt;
Cascading Style Sheets (CSS)&lt;/li&gt;
&lt;li&gt;Grouping concerns&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Working with a component architecture&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id="the-proximity-principle-explained"&gt;The proximity principle explained&lt;/h2&gt;

&lt;p&gt;The proximity principle instructs us to group similar items together. And to separate dissimilar items. Generally, we do this with whitespace, but we can also break code into separate files.&lt;/p&gt;

&lt;p&gt;You can see this in the design of interfaces:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcraft-code.dev%2Fimages%2Fproximity-principle.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcraft-code.dev%2Fimages%2Fproximity-principle.svg" alt="The proximity principle at work."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see above, A, B, and C are quite similar. Because we have grouped them together, a viewer will immediately grasp this relationship.&lt;/p&gt;

&lt;p&gt;X, Y, and Z are somewhat less similar, but still related. But not to A, B, and C. The clear grouping and separation makes this obvious.&lt;/p&gt;

&lt;p&gt;The circle 0 appears unique.&lt;/p&gt;

&lt;p&gt;But what happens if we ignore the proximity principle? Nothing good:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcraft-code.dev%2Fimages%2Funproximity-principle.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcraft-code.dev%2Fimages%2Funproximity-principle.svg" alt="Lack of grouping creates confusion and cognitive load."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This same principle applies to code. Why wouldnʼt it? The truth is that &lt;strong&gt;all the design principles apply to written code as well&lt;/strong&gt;. Design is design and everything has a design. But is that design arbitrary and accidental? Or thoughtfull and deliberate?&lt;/p&gt;

&lt;h2 id="applying-the-proximity-principle-to-code"&gt; Applying the proximity principle to code&lt;/h2&gt;

&lt;p&gt;We can apply the &lt;strong&gt;proximity principle&lt;/strong&gt; to our code. Most of us already do.&lt;/p&gt;

&lt;p&gt;The first level of proximity, or better, &lt;em&gt;use of whitespace,&lt;/em&gt; is &lt;strong&gt;within each line of code.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Withoutwhitespacecognitiveloadgoesupdramatically.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;How much longer did it take you to parse that sentence? Without spaces you have to make an effort to find the individual words. Our guess is that &lt;strong&gt;the cognitive load increased by at least an order of magnitude.&lt;/strong&gt; Thatʼs ten times!&lt;/p&gt;

&lt;p&gt;The letters that make up a word have a stronger relationship to each other than to the letters in an adjacent word. So we group the letters of each word and put a space between them and the next word.&lt;/p&gt;

&lt;p&gt;Thus, it is difficult to understand why some coders prefer &lt;code&gt;num=777&lt;/code&gt; to &lt;code&gt;num = 777&lt;/code&gt;. There are &lt;strong&gt;three&lt;/strong&gt; different objects here. The first is a variable name, &lt;code&gt;num&lt;/code&gt;. The second is the assignment operator, &lt;code&gt;=&lt;/code&gt;. The last is a number, &lt;code&gt;777&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The whitespace makes clear that they serve different purposes. Placing them on the same line and away from other similar code makes clear that they have a relationship. Namely, that they are parts of a single statement.&lt;/p&gt;

&lt;p&gt;It is no different in prose. We group words into phrases. We group phrases into sentences. We group sentences into paragraphs and paragraphs into larger blocks, such as sections.&lt;/p&gt;

&lt;p&gt;These groups should not be capricious. If we move punctuation arbitrarily, clarity suffers. And although many people appear to have abandoned the idea of a paragraph as a container for a single thought, that remains a better way.&lt;/p&gt;

&lt;p&gt;It astonishes us how many programmers argue that this is all a matter of personal style or opinion. &lt;strong&gt;It is not.&lt;/strong&gt; The difference in cognitive load is &lt;em&gt;measurable.&lt;/em&gt; What gain offsets this loss of clarity? The argument for reducing file size ended long ago.&lt;/p&gt;

&lt;p&gt;We can also group items using blank lines. This, too, is widely ignored. Weʼre not sure what the benefit is. Failure to use the proximity principle makes for difficult code.&lt;/p&gt;

&lt;p&gt;Why write code like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getOrdinal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cardinalNumber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onesDigit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cardinalNumber&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onesDigit&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cardinalNumber&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;st`&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onesDigit&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cardinalNumber&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;nd`&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onesDigit&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cardinalNumber&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;rd`&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cardinalNumber&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;th`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we can write it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getOrdinal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cardinalNumber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onesDigit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cardinalNumber&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onesDigit&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cardinalNumber&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;st`&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onesDigit&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cardinalNumber&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;nd`&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onesDigit&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cardinalNumber&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;rd`&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cardinalNumber&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;th`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Good use of whitespace makes code easy to read and understand.&lt;/p&gt;

&lt;p&gt;Granted, weʼve taken our compressed example to an extreme. Few would write code quite &lt;em&gt;that&lt;/em&gt; compressed. But the point is that we already apply &lt;strong&gt;design principles&lt;/strong&gt; to our code to make it much more readable. And that makes a big difference.&lt;/p&gt;

&lt;p&gt;In a similar way, we use syntax highlighting for &lt;strong&gt;contrast&lt;/strong&gt; and to show functional relationships. Who shuns syntax highlighting? We use it to make clear instantly the function of each bit of code: This is a number. This is a string. This is a keyword. This is an operator.&lt;/p&gt;

&lt;p&gt;Way back in 1995, Robin Williams (not &lt;em&gt;that&lt;/em&gt; one) wrote &lt;a href="https://www.goodreads.com/book/show/41597.The_Non_Designer_s_Design_Book" rel="noopener noreferrer"&gt;a book on design principles&lt;/a&gt; for developers. We at Craft Code have been applying its principles ever since.&lt;/p&gt;

&lt;p&gt;She suggested the acronym “CRAP” as an easy way to remember four key design principles. These are &lt;strong&gt;Contrast, Repetition, Alignment, and Proximity.&lt;/strong&gt; Misuse them and what do you get? Crap.&lt;/p&gt;

&lt;p&gt;How much better to use &lt;em&gt;all&lt;/em&gt; the design principles in our code! How is code really any different from prose?&lt;/p&gt;

&lt;h2 id="forget-separation-of-concerns"&gt; Forget separation of concerns&lt;/h2&gt;

&lt;p&gt;Think &lt;strong&gt;proximity.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Browsers understand three types of code: HTML, CSS, and JavaScript. Weʼll leave WASM for another day.&lt;/p&gt;

&lt;p&gt;We can send HTML, CSS, and JavaScript to the browser. Or we can send the bare minimum to load a script and then use JavaScript to manipulate the DOM directly.&lt;/p&gt;

&lt;p&gt;There are times when the latter approach makes sense. But most of the time, it fails the proximity principle. HTML, CSS, and JavaScript serve different functions. As they are doing different things, it makes sense to group them and move them away from each other.&lt;/p&gt;

&lt;p&gt;Skip the next two sections if this is all review to you.&lt;/p&gt;

&lt;h3 id="html"&gt; HyperText Markup Language (HTML)&lt;/h3&gt;

&lt;p&gt;HTML is the first language of the World Wide Web. Tim Berners-Lee created it to mark up physics documents for sharing. He based it on SGML, the Standard Generalized Markup Language. The goal of SGML is to make documents machine-readable.&lt;/p&gt;

&lt;p&gt;HTML is all about &lt;strong&gt;content.&lt;/strong&gt; We want to organize that content into a set of nested “boxes”. We do that by marking the start and finish of each box with sets of angle brackets we call “tags”. To make clear where a box ends, we include a slash in the closing tag: &lt;code&gt;&amp;lt;&amp;gt;content&amp;lt;/&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But we also want to say whatʼs in the box. To do this, we apply a &lt;strong&gt;label&lt;/strong&gt; to the contents. And to be extra clear, we add this label to both the opening and closing tags: &lt;code&gt;&amp;lt;em&amp;gt;emphasized&amp;lt;/em&amp;gt;&lt;/code&gt;. Now our box has &lt;em&gt;meaning.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We can also provide more information about what is in the “box”. We call this information &lt;strong&gt;metadata&lt;/strong&gt; and we add it using &lt;strong&gt;attributes&lt;/strong&gt; as key-value pairs. We add the attributes only to the opening HTML tag. For example: &lt;code&gt;&amp;lt;a href="https://craft-code.dev/"&amp;gt;Craft Code&amp;lt;/a&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We also allow some empty elements and a few empty attributes.&lt;/p&gt;

&lt;p&gt;With good use of “CRAP”, we can make our HTML easy to read. We use a monospace font to ensure &lt;strong&gt;alignment.&lt;/strong&gt; Then we use &lt;strong&gt;indentation&lt;/strong&gt; to show nesting. This makes our code comprehensible at a glance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt;
      &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1"&lt;/span&gt;
      &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt;
    &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;This is a title&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;main&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Consectetur adipiscing elit&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent in mauris tempus &lt;span class="nt"&gt;&amp;lt;strong&amp;gt;&lt;/span&gt;diam imperdiet gravida&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&lt;/span&gt; ut non diam. Maecenas in malesuada velit, ut porttitor libero. Proin vitae condimentum metus.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Machine readable &lt;em&gt;and&lt;/em&gt; easily understood by humans.&lt;/p&gt;

&lt;p&gt;Note the use of blank lines to group content by function.&lt;/p&gt;

&lt;p&gt;If we deliver the above code to a browser, it will display our content. We have not provided any directions for how to style it. But the browser makers will still want to &lt;strong&gt;reduce cognitive load&lt;/strong&gt; for the users.&lt;/p&gt;

&lt;p&gt;So the browser will apply a &lt;strong&gt;default stylesheet&lt;/strong&gt; to the content.&lt;/p&gt;

&lt;p&gt;The browser will display the &lt;code&gt;h1&lt;/code&gt; element in a larger font and on its own line. It will add a new line after the &lt;code&gt;p&lt;/code&gt; (paragraph) element. But it will bold the contents of the &lt;code&gt;strong&lt;/code&gt; element and let it flow inline.&lt;/p&gt;

&lt;p&gt;The default stylesheet does all this. It helps to make plain HTML content &lt;a href="http://localhost:3002/essays/code/responsive-by-default" rel="noopener noreferrer"&gt;responsive and accessible.&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The most common mistake made by those new to HTML is to &lt;strong&gt;confuse the default stylesheet with the HTML itself.&lt;/strong&gt; This is an error. The &lt;code&gt;p&lt;/code&gt; element &lt;strong&gt;does not have a style.&lt;/strong&gt; Neither does the &lt;code&gt;h1&lt;/code&gt; element.&lt;/p&gt;

&lt;p&gt;It is the &lt;strong&gt;default stylesheet&lt;/strong&gt; that has the styles.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;p&lt;/code&gt; element has a &lt;em&gt;meaning&lt;/em&gt;. It represents a set of one or more sentences that express a single idea or theme.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;h1&lt;/code&gt; element also has a meaning. It represents the top-level heading (title) for a document or article. There is typically only one per web page.&lt;/p&gt;

&lt;p&gt;We call this association of meaning with HTML elements “semantics”. Thus, &lt;a href="http://localhost:3002/glossary/s#semantic-html" rel="noopener noreferrer"&gt;semantic HTML.&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The earliest versions of HTML included elements intended to control presentation. This was before CSS. Those elements (e.g., &lt;code&gt;font&lt;/code&gt;) are obsolete now. Later versions of HTML abandoned them in favor of keeping the styling separate. Wise move.&lt;/p&gt;

&lt;h3 id="css"&gt;Cascading Style Sheets (CSS)&lt;/h3&gt;

&lt;p&gt;Cascading Style Sheets came along less than a decade into the World Wide Web. By the turn of the millennium, they were in widespread use.&lt;/p&gt;

&lt;p&gt;That said, &lt;strong&gt;standardizing CSS&lt;/strong&gt; across browsers took quite a bit longer. As did fixing some poor choices. But now pretty much every web site or app uses CSS to control presentation.&lt;/p&gt;

&lt;p&gt;As HTML is for marking up &lt;strong&gt;content,&lt;/strong&gt; CSS is for &lt;strong&gt;styling&lt;/strong&gt; HTML. CSS uses &lt;strong&gt;selectors&lt;/strong&gt; to grab the HTML element (or elements) that it wants. Then it applies a set of style properties to that element.&lt;/p&gt;

&lt;p&gt;This is easier in theory than in practice. Achieving mastery in CSS is no mean feat — as any good front-end developer will tell you.&lt;/p&gt;

&lt;p&gt;But we can make CSS much more comprehensible! Highlight the syntax and use the proximity principle, as below. You can almost feel the cognitive load melting away.&lt;/p&gt;

&lt;p&gt;Here is some poorly-formatted CSS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;figure&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;flex-direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;column-reverse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;stretch&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--space-m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1.25rem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;space-between&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;0.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;figure&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;figcaption&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;figure&lt;/span&gt;&lt;span class="nc"&gt;.is-indexed&lt;/span&gt; &lt;span class="nt"&gt;figcaption&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;"Figure "&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;figures&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="s1"&gt;": "&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some folks love this. But why?&lt;/p&gt;

&lt;p&gt;Below is that same CSS. But weʼve &lt;strong&gt;alphabetized&lt;/strong&gt; the properties. Weʼve added whitespace to make the property name and its value easier to distinguish. And blank lines separate the selectors. &lt;strong&gt;The reduction in cognitive load is measurable.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;figure&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;stretch&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;flex-direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;column-reverse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;space-between&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--space-m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1.25rem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;figure&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;figcaption&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;figure&lt;/span&gt;&lt;span class="nc"&gt;.is-indexed&lt;/span&gt; &lt;span class="nt"&gt;figcaption&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"Figure "&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;figures&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="s1"&gt;": "&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Proper whitespacing and more makes CSS much easier to read.&lt;/p&gt;

&lt;h3 id="grouping-concerns"&gt;Grouping concerns&lt;/h3&gt;

&lt;p&gt;We can apply this same proximity principle to our “concerns”. We put our content, structure, and semantics in one place. Then we put our instructions for presentation somewhere separate, but nearby.&lt;/p&gt;

&lt;p&gt;In still a third location we put our algorithmic behavior. That behavior might be transferring data to a back end with &lt;code&gt;fetch&lt;/code&gt;. Or it might be altering structure, semantics, or style on the fly. Or validating user input.&lt;/p&gt;

&lt;p&gt;We can do this grouping in a &lt;strong&gt;single file,&lt;/strong&gt; or we can spit our code out into &lt;strong&gt;files of different types.&lt;/strong&gt; But there is another way we can group like code: into reusable components.&lt;/p&gt;

&lt;h2 id="working-with-a-component-architecture"&gt;Working with a component architecture&lt;/h2&gt;

&lt;p&gt;The aim of &lt;em&gt;all&lt;/em&gt; programming is to break complex problems down into &lt;strong&gt;simpler problems.&lt;/strong&gt; And to continue breaking them down until they become comprehensible and solvable.&lt;/p&gt;

&lt;p&gt;Then to compose the solutions into a single solution to the original problem.&lt;/p&gt;

&lt;p&gt;In short, much of the skill in programming is not the coding itself. It is the problem analysis in which we decide where to make the cuts. This is &lt;strong&gt;problem decomposition.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A common guide for how to do this is the &lt;a href="http://localhost:3002/glossary/s#srp" rel="noopener noreferrer"&gt;Single Responsibility Principle.&lt;/a&gt; You may know this as part of the &lt;a href="https://en.wikipedia.org/wiki/SOLID" rel="noopener noreferrer"&gt;SOLID&lt;/a&gt; design principles. We can apply this principle well beyond the limits of Object-Oriented Programming (OOP).&lt;/p&gt;

&lt;p&gt;True, the single responsibility principle comes from OOP. But it is actually about modularization of code. At the most basic level we have utility functions and &lt;strong&gt;components.&lt;/strong&gt; On the front end, these components generally provide UI widgets.&lt;/p&gt;

&lt;p&gt;We can then compose our components into larger groups of components. Call them modules. This enhances resuability, but it also is vital for reducing cognitive load. And each component (or function) should do &lt;strong&gt;one&lt;/strong&gt; (1) thing well.&lt;/p&gt;

&lt;p&gt;In &lt;a href="http://localhost:3002/essays/critique/the-proximity-principle-part-2" rel="noopener noreferrer"&gt;Part 2&lt;/a&gt; of this article, we give a good example of what not to do.&lt;/p&gt;

</description>
      <category>proximityprinciple</category>
      <category>separationofconcerns</category>
      <category>webdev</category>
      <category>devrel</category>
    </item>
    <item>
      <title>DOM to JSON and back</title>
      <dc:creator>Charles F. Munat</dc:creator>
      <pubDate>Sun, 14 Jan 2024 22:26:07 +0000</pubDate>
      <link>https://forem.com/craft-code/dom-to-json-and-back-3mgc</link>
      <guid>https://forem.com/craft-code/dom-to-json-and-back-3mgc</guid>
      <description>&lt;p&gt;&lt;strong&gt;We can persist and rehydrate DOM objects with simple vanilla JS.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;5-6 minutes, 1332 words, 4th grade&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here we will create &lt;strong&gt;two simple but powerful JavaScript functions.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The first, &lt;code&gt;jsToDom&lt;/code&gt;, will take a JavaScript (or JSON) object and turn it into a DOM object that we can insert into our page. The second, &lt;code&gt;domToJs&lt;/code&gt;, does the reverse. It takes a DOM object and converts it to JS.&lt;/p&gt;

&lt;p&gt;Now we can stringify it and persist it in our database. And rehydrate it at will.&lt;/p&gt;

&lt;p&gt;As you may know, JSON does not recognize functions. So we will need a way to deal with our event listeners. Donʼt worry! Weʼve got it covered.&lt;/p&gt;

&lt;p&gt;Check out our &lt;a href="https://sitebender.io/examples/dom-from-json/index.html"&gt;example of DOM to JSON and back&lt;/a&gt; in action. We will explain it below.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A key axiom of Craft Code is &lt;a href="https://craft-code.dev/axioms/less-is-more"&gt;less is more&lt;/a&gt;. This influences several of the Craft Code &lt;a href="https://craft-code.dev/methods"&gt;methods&lt;/a&gt;, such as &lt;a href="https://craft-code.dev/methods/code-just-in-time"&gt;code just in time&lt;/a&gt; and &lt;a href="https://craft-code.dev/methods/keep-it-simple"&gt;keep it simple&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This means that we donʼt rush to load up dozens of frameworks, libraries, and other dependencies. Instead, we start with nothing: &lt;strong&gt;zero dependencies.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We build the site structure with semantically-correct and accessible HTML. Then we add just enough CSS to make it attractive, user-friendly, and responsive.&lt;/p&gt;

&lt;p&gt;Finally, we add JavaScript to &lt;strong&gt;progressively enhance&lt;/strong&gt; the user experience. &lt;em&gt;But only if we need it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This vanilla approach works very well. One goal of the Craft Code effort is to see how far we can go before we have to add a dependency on someone elseʼs code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The code
&lt;/h2&gt;

&lt;p&gt;For the impatient, letʼs take a look at the final code. Then weʼll explain it. The next two functions are the totality of the module. The rest are specific to this example.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note: this is about concepts, not production-ready code. This is a first pass and might could use some refactoring.&lt;/strong&gt; YMMV.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ./modules/js-to-dom.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;jsToDom&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;js&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tagName&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;js&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;elem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tagName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;attr&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;child&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;child&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;jsToDom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;child&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createTextNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;child&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
          &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
          &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`./&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="s2"&gt;.js`&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;

      &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;setDataEvents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;js&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;elem&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;setDataEvents&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;eventString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eventString&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data-events&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;eventString&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We grab the &lt;code&gt;tagName&lt;/code&gt; from the JSON and create a DOM element of that type. Then we add the attributes to that element. Then we apply &lt;code&gt;jsToDom&lt;/code&gt; recursively on the &lt;code&gt;children&lt;/code&gt;, appending them to the element.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;events&lt;/code&gt; object is pretty clever, IOHO. The keys are the names of the events (e.g., &lt;code&gt;click&lt;/code&gt;) and the values are the names of the handler functions. We will import those functions only when needed. See an example below.&lt;/p&gt;

&lt;p&gt;We also create a string representation of the events object. We could have simply stringified it, but we wanted to make it human readable, so we wrote our own (lines #39 to #52).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ./modules/dom-to-js.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;domToJs&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dom&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;childNodes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tagName&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dom&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;eventList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data-events&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;eventList&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;attrs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;localName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data-events&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;tagName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt;
    &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;childNodes&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;child&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;childNodes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nodeType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TEXT_NODE&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nodeValue&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;domToJs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;child&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a bit tricky as &lt;em&gt;nothing&lt;/em&gt; in the DOM is simple. Go figure. We explain below.&lt;/p&gt;

&lt;p&gt;This extracts the &lt;code&gt;attributes&lt;/code&gt;, the &lt;code&gt;childNodes&lt;/code&gt;, and &lt;code&gt;tagName&lt;/code&gt; from the passed DOM element. Then we use these to create a simple JS/JSON object, recursing through the child nodes.&lt;/p&gt;

&lt;p&gt;We pull the &lt;code&gt;data-events&lt;/code&gt; attribute out and treat it separately. We parse the value back into an actual object and add it at the &lt;code&gt;events&lt;/code&gt; key.&lt;/p&gt;

&lt;p&gt;The output is JS, but we can stringify it to JSON as required. The JSON shown below is a typical example. It creates our test form.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tagName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FORM"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"attributes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"form"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"events"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"focusin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"log"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"submit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"parse-submission"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"children"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"tagName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TEXTAREA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"attributes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"data-type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"json"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"children"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;tagName&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;DIV&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;attributes&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;class&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;sb-test&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;data-type&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;sb-test-id&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;},&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;events&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;log&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;},&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;children&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:[{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;tagName&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;STRONG&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;children&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:[&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Bob's yer uncle.&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;]}]}"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"tagName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BUTTON"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"attributes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"aria-label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Run this baby"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"submit"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"children"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"Run"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We import this JSON and pass it to &lt;code&gt;jsToDom&lt;/code&gt; in our &lt;code&gt;index.js&lt;/code&gt; file below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// index.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;jsToDom&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./modules/js-to-dom.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;formJson&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./modules/form-json.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;outJson&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./modules/out-json.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;injectForm&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;main&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;main&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;jsToDom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outJson&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;jsToDom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formJson&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;globalThis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DOMContentLoaded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;injectForm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pretty self-explanatory. Now, how do we handle our events?&lt;/p&gt;

&lt;p&gt;Simple. We take our &lt;code&gt;events&lt;/code&gt; object from the passed JSON/JS. Then we loop through the keys, which are the event types. The values are the &lt;strong&gt;names&lt;/strong&gt; of the handler functions.&lt;/p&gt;

&lt;p&gt;We add an event listener for each type and assign it the default function from the module with that name. For example, our output &lt;code&gt;div&lt;/code&gt; gets a &lt;code&gt;click&lt;/code&gt; handler called “log”. This function is in &lt;code&gt;./modules/log.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We import the handler: &lt;code&gt;(await import("./log.js")).default&lt;/code&gt;. We assign it to &lt;code&gt;handler&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then we add it like this: &lt;code&gt;addEventListener("click", handler)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Drop dead simple. And we only import the modules that we need. See the actual &lt;code&gt;log&lt;/code&gt; handler below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ./modules/log.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;log&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;tagName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kinda dumb, but it is merely an example. We add this &lt;code&gt;log&lt;/code&gt; function as a &lt;code&gt;click&lt;/code&gt; handler on our output &lt;code&gt;strong&lt;/code&gt; element and as a &lt;code&gt;focusin&lt;/code&gt; handler on our &lt;code&gt;form&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;submit&lt;/code&gt; handler for the form is a bit more exciting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ./modules/parse-submission.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;domToJs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./dom-to-js.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;jsToDom&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./js-to-dom.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;textarea&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;textarea&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.out&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;js&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;textarea&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;jsToDom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;js&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newForm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;domToJs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;main&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;jsToDom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newForm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We canʼt store functions in JSON, so we put them into modules. Then we will import them as needed when we rehydrate the DOM elements.&lt;/p&gt;

&lt;p&gt;We attach &lt;code&gt;parseSubmission&lt;/code&gt; as the &lt;code&gt;submit&lt;/code&gt; handler for our &lt;code&gt;form&lt;/code&gt; element.&lt;/p&gt;

&lt;h2&gt;
  
  
  What can we do with this?
&lt;/h2&gt;

&lt;p&gt;Ooo. All sorts of cool things.&lt;/p&gt;

&lt;h3&gt;
  
  
  Easy element creation
&lt;/h3&gt;

&lt;p&gt;Instead of messing around with &lt;code&gt;createElement&lt;/code&gt;, &lt;code&gt;setAttribute&lt;/code&gt;, etc., we can use &lt;code&gt;jsToDom&lt;/code&gt;. We pass it a JS or JSON object representing the DOM elements we want.&lt;/p&gt;

&lt;p&gt;We create handler functions ahead of time in modules. When we need an event listener, &lt;code&gt;jsToDom&lt;/code&gt; imports it &lt;em&gt;just in time&lt;/em&gt; and assigns it to the element.&lt;/p&gt;

&lt;p&gt;This works like Reactʼs &lt;code&gt;createElement&lt;/code&gt; function. Or a library such as &lt;a href="https://github.com/hyperhype/hyperscript"&gt;hyperscript&lt;/a&gt;. Sure, weʼd prefer JSX for its much reduced cognitive load. But our alternative here is the DOM methods such as &lt;code&gt;createElement&lt;/code&gt;. Unless we want to load up a bulky library such as React, that is.&lt;/p&gt;

&lt;p&gt;We donʼt.&lt;/p&gt;

&lt;p&gt;Suppose I wanted to inject a password field with a show/hide button. First, we create a toggle handler such as this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ./modules/toggle-visibility.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;closest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.form-field&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hide&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aria-label&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hide password.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;show&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aria-label&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Show password.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now I can call &lt;code&gt;jsToDom&lt;/code&gt; with the following JSON and it will create my password input. Try pasting it into the &lt;a href="http://sitebender.io/examples/dom-from-json/index.html"&gt;example form&lt;/a&gt;. Remember that the click event handler is already available at &lt;code&gt;./modules/toggle-visibility.js&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tagName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DIV"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"attributes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"class"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"form-field"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"events"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"click"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"log"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"children"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"tagName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"INPUT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"attributes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"password"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"tagName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BUTTON"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"attributes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"aria-label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Show password."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"class"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xx-toggle-password"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"button"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"events"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"click"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"toggle-visibility"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"children"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"show"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We hope that it is straightforward how all this works.&lt;/p&gt;

&lt;h3&gt;
  
  
  Easy element persistance
&lt;/h3&gt;

&lt;p&gt;What if I want to save current UI state? We can use &lt;code&gt;domToJs&lt;/code&gt; to do just that.&lt;/p&gt;

&lt;p&gt;We took the example page (linked above) and passed the &lt;code&gt;html&lt;/code&gt; element to &lt;code&gt;domToJs&lt;/code&gt;. Then we stringified it. Now we have preserved both the &lt;code&gt;head&lt;/code&gt; and &lt;code&gt;body&lt;/code&gt; elements.&lt;/p&gt;

&lt;p&gt;So we can take a blank HTML document like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"./modules/make-page.js"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we can use our persisted JSON &lt;code&gt;head&lt;/code&gt; and &lt;code&gt;body&lt;/code&gt; elements to create the page on the fly. We can store the JSON in a database or load it from an API.&lt;/p&gt;

&lt;p&gt;Below is the code minus the JSON. Or view the &lt;a href="http://sitebender.io/examples/dom-from-json/modules/make-page.js"&gt;actual code.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then &lt;a href="https://sitebender.io/examples/dom-from-json/make-page.html"&gt;see it in action.&lt;/a&gt; View the source on that page to see what we mean.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;jsToDom&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./js-to-dom.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="nx"&gt;globalThis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DOMContentLoaded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;head&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replaceWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;jsToDom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&gt;/* head JSON here */&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

  &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replaceWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;jsToDom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&gt;/* body JSON here */&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A whole page generated from simple JSON!&lt;/p&gt;

&lt;h3&gt;
  
  
  Data-driven user interfaces
&lt;/h3&gt;

&lt;p&gt;One idea that we have been promoting for several years now is that of a data-driven interface. Weʼve got an article in the pipeline on that coming soon. But we can give a quick overview here.&lt;/p&gt;

&lt;p&gt;The idea is quite simple. The easiest example to visualize is automated form rendering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Forms collect data.&lt;/strong&gt; Typically, that data is then persisted in a database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Databases have schemas.&lt;/strong&gt; That means that the database already knows the types it expects. If we have defined our schema well, then it knows those types precisely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Particular data use particular interface widgets.&lt;/strong&gt; For example, an email address would use an &lt;code&gt;input&lt;/code&gt; element of type “email”. An integer might use an input element of type “number” with its &lt;code&gt;step&lt;/code&gt; attribute set to “1”.&lt;/p&gt;

&lt;p&gt;An enum might use a &lt;code&gt;select&lt;/code&gt; or &lt;code&gt;radio&lt;/code&gt; buttons or a &lt;code&gt;checkbox&lt;/code&gt; group.&lt;/p&gt;

&lt;p&gt;From this schema we should be able to determine how to both &lt;strong&gt;display and validate&lt;/strong&gt; that data. After all, there is only a small number of widgets.&lt;/p&gt;

&lt;p&gt;So what if the response to our database query for the data included the schema? Some GraphQL queries already make this possible. From that schema, we can generate validation functions. &lt;em&gt;And&lt;/em&gt; we know which widgets to display.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So we can generate our form and our validators automatically.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Best of all, we have a single source of truth: our database schema.&lt;/p&gt;

&lt;p&gt;In a coming article, we will explain how easy it is to achieve this.&lt;/p&gt;

&lt;p&gt;(There is an advanced version of this that uses a &lt;a href="https://en.wikipedia.org/wiki/SHACL"&gt;SHACL&lt;/a&gt; ontology and a &lt;a href="https://en.wikipedia.org/wiki/Triplestore"&gt;triple store&lt;/a&gt; such as &lt;a href="https://jena.apache.org/documentation/fuseki2/"&gt;Fuseki&lt;/a&gt;. We then use &lt;a href="https://en.wikipedia.org/wiki/SPARQL"&gt;SPARQL&lt;/a&gt; queries to generate the HTML, CSS, and JS straight from the database. Wee ha! Weʼll get to that sometime soon, too. Promise.)&lt;/p&gt;

&lt;p&gt;If anyone requests it, weʼll give a detailed explanation of the above code in a separate article. This one is long enough.&lt;/p&gt;

</description>
      <category>craftcode</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>dom</category>
    </item>
    <item>
      <title>Adding simple PubSub, part 2</title>
      <dc:creator>Charles F. Munat</dc:creator>
      <pubDate>Mon, 01 Jan 2024 21:50:46 +0000</pubDate>
      <link>https://forem.com/craft-code/adding-simple-pubsub-part-2-368e</link>
      <guid>https://forem.com/craft-code/adding-simple-pubsub-part-2-368e</guid>
      <description>&lt;p&gt;&lt;strong&gt;From event delegation to true pubsub.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Less than 5 minutes, 1178 words, 4th grade&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In our &lt;a href="https://dev.to/craft-code/adding-simple-pubsub-part-1-4hi0"&gt;previous article&lt;/a&gt; we built a simple event delegation system. Our intent was to develop that into a full &lt;strong&gt;PubSub&lt;/strong&gt; system. We can use this PubSub system to create an &lt;strong&gt;event-driven architecture.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It will be better if we split the event delegation feature from the PubSub part so we are clear what weʼre doing. We can begin by creating a &lt;code&gt;listeners&lt;/code&gt; object to replace our &lt;code&gt;subscriptions&lt;/code&gt; object. Weʼll store our listeners here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will also rename our &lt;code&gt;subscribe&lt;/code&gt; function to &lt;code&gt;register&lt;/code&gt;. We will move the event listener callback into our &lt;code&gt;register&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;castEvent&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./cast-event.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;listeners&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./listeners.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eventType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;castEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eventType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;listeners&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;listeners&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;

      &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;listeners&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;]?.[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]?.(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;listeners&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It should be obvious what this does (see &lt;a href="https://dev.to/craft-code/adding-simple-pubsub-part-1-4hi0"&gt;our previous essay&lt;/a&gt;). Now letʼs change our &lt;code&gt;unsubscribe&lt;/code&gt; function to an &lt;code&gt;unregister&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;castEvent&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./cast-event.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;listeners&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./listeners.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eventType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;castEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eventType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="nx"&gt;listeners&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;]?.[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;listeners&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="nx"&gt;listeners&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, this works exactly the same as our previous &lt;code&gt;unsubscribe&lt;/code&gt; function. Now we have moved our event delegation system into itʼs own module. Now to our PubSub system.&lt;/p&gt;

&lt;p&gt;Note that after we unregister the last listener, we clean up after ourselves.&lt;/p&gt;

&lt;p&gt;As a reminder, here is our &lt;code&gt;castEvent&lt;/code&gt; function code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;blur&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;focusout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;focus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;focusin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Adding the PubSub system
&lt;/h2&gt;

&lt;p&gt;We copied the code above into new files. This left our previous &lt;code&gt;pubsub&lt;/code&gt; files. So, we already have our basic PubSub module. But we will need to update them a bit.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;subscriptions&lt;/code&gt; shared object remains as before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our &lt;code&gt;unsubscribe&lt;/code&gt; function is also as before. But as we are no longer dealing with browser events, we donʼt need the &lt;code&gt;castEvent&lt;/code&gt; function anymore. Those browser events go to our event delegator system above, not to PubSub.&lt;/p&gt;

&lt;p&gt;We have no ID, but weʼll want a way to unsubscribe, so we generate a UUID and return that from our &lt;code&gt;subscribe&lt;/code&gt; function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;subscriptions&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./subscriptions.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cb&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is pretty self-explanatory, I hope. Our &lt;code&gt;subscribe&lt;/code&gt; function takes two parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the event &lt;code&gt;type&lt;/code&gt; to listen for, e.g., &lt;code&gt;EMAIL_UPDATED&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;callback&lt;/code&gt; function to call when some component publishes an event of this type&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;We&lt;/strong&gt; will decide which events to publish and to subscribe to. The nice thing about a PubSub system is that &lt;strong&gt;we can create any event we like.&lt;/strong&gt; We are not limited to browser events.&lt;/p&gt;

&lt;p&gt;First, we ensure that there is an object associated with this subscription type.&lt;/p&gt;

&lt;p&gt;If this is the first subscription of this &lt;code&gt;type&lt;/code&gt;, then we add a listener for this event type. Our callback loops through all the individual callbacks stored in the &lt;code&gt;subscriptions&lt;/code&gt; object. It calls each in turn, passing the current &lt;code&gt;event&lt;/code&gt; object.&lt;/p&gt;

&lt;p&gt;Then we generate a random UUID as our &lt;code&gt;token&lt;/code&gt;. We use this to assign the passed-in callback to this topic and token (line #16). Finally, we return the token for use by the &lt;code&gt;unsubscribe&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;What about our &lt;code&gt;unsubscribe&lt;/code&gt; function? It is simple as can be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;subscriptions&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./subscriptions.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="nx"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;]?.[&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We take as parameters the &lt;code&gt;topic&lt;/code&gt; and the &lt;code&gt;token&lt;/code&gt;. Then we delete the &lt;code&gt;callback&lt;/code&gt; for that topic/token in the &lt;code&gt;subscriptions&lt;/code&gt; object.&lt;/p&gt;

&lt;p&gt;Finally, if there are no remaining subscribers for that &lt;code&gt;topic&lt;/code&gt;, we delete the topic.&lt;/p&gt;

&lt;p&gt;Now we need a &lt;code&gt;publish&lt;/code&gt; function to allow us to publish to custom events. Here it is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;detail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;customEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CustomEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;bubbles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispatchEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We pass the &lt;code&gt;topic&lt;/code&gt;, which is our custom event such as &lt;code&gt;EMAIL_UPDATED&lt;/code&gt;. And we pass a &lt;code&gt;detail&lt;/code&gt; object. This is the data that our custom event will carry. It could be anything. See our examples below.&lt;/p&gt;

&lt;p&gt;On lines #2 to #8, we create a &lt;code&gt;CustomEvent&lt;/code&gt;. We assign it our &lt;code&gt;topic&lt;/code&gt; as the event type. This is what our subscribers are listening for. We also pass the &lt;code&gt;detail&lt;/code&gt; object and set event bubbling to &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then on line #10 we call &lt;code&gt;dispatchEvent&lt;/code&gt; on the &lt;code&gt;document.body&lt;/code&gt; element to dispatch our custom event. By dispatching it and listening for it on the &lt;code&gt;body&lt;/code&gt; element, we do not need to bubble it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Our PubSub system in action
&lt;/h2&gt;

&lt;p&gt;We add the PubSub system to our window (&lt;code&gt;globalThis&lt;/code&gt;) object. We add a single object representing our namespace, &lt;code&gt;_xx&lt;/code&gt;. Then we add our various modules to that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;listeners&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./modules/listeners.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;publish&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./modules/publish.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;register&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./modules/register.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;subscribe&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./modules/subscribe.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;subscriptions&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./modules/subscriptions.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;unregister&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./modules/unregister.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;unsubscribe&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./modules/unsubscribe.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="nx"&gt;globalThis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_xx&lt;/span&gt; &lt;span class="o"&gt;??=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;globalThis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_xx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;listeners&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;register&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;unregister&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;« PubSub and event delegation enabled. »&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;[There must be a better way to do this. Ideas?]&lt;/p&gt;

&lt;p&gt;Weʼve set up &lt;a href="http://localhost:3011/examples/pubsub-2/index.html"&gt;an example of the PubSub system in use.&lt;/a&gt; You can see the various modules there:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://sitebender.io/examples/pubsub-2/modules/cast-event.js"&gt;/modules/cast-event.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sitebender.io/examples/pubsub-2/modules/listeners.js"&gt;/modules/listeners.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sitebender.io/examples/pubsub-2/modules/publish.js"&gt;/modules/publish.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sitebender.io/examples/pubsub-2/modules/register.js"&gt;/modules/register.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sitebender.io/examples/pubsub-2/modules/subscribe.js"&gt;/modules/subscribe.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sitebender.io/examples/pubsub-2/modules/subscriptions.js"&gt;/modules/subscriptions.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sitebender.io/examples/pubsub-2/modules/unregister.js"&gt;/modules/unregister.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sitebender.io/examples/pubsub-2/modules/unsubscribe.js"&gt;/modules/unsubscribe.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sitebender.io/examples/pubsub-2/index.js"&gt;index.js&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now letʼs set up a &lt;a href="https://sitebender.io/examples/pubsub-2"&gt;simple test page.&lt;/a&gt; Imagine that we have a set of individually-editable fields, such as name, email, and phone:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"name-editor"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Name&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Update&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"email-editor"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Email&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Update&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"phone-editor"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"phone"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Phone&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"phone"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"phone"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"tel"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Update&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we will include some temporary elements to permit us to display our events:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;pre&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"name-output"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;pre&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"email-output"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;pre&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"phone-output"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now in our script we first import the PubSub module and then register our event listeners for &lt;code&gt;submit&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"./index.js"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;globalThis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DOMContentLoaded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;globalThis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_xx&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name-editor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

          &lt;span class="nx"&gt;globalThis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_xx&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NAME_UPDATED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// register submit listeners for email and phone, too&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we import our PubSub system as a module. Then we add a &lt;code&gt;DOMContentLoaded&lt;/code&gt; event listener to run our registration code after the DOM loads.&lt;/p&gt;

&lt;p&gt;Next we use the &lt;code&gt;register&lt;/code&gt; function of our event delegation system to register a &lt;code&gt;submit&lt;/code&gt; handler. This listens for a &lt;code&gt;submit&lt;/code&gt; event on the form with the passed ID of “name-editor”. On submit, it calls the passed callback, which:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;prevents the default submission so that we donʼt reload the page&lt;/li&gt;
&lt;li&gt;calls the &lt;code&gt;publish&lt;/code&gt; PubSub function to publish our custom event. We will give it the type &lt;code&gt;NAME_UPDATED&lt;/code&gt;. And weʼll pass a &lt;code&gt;details&lt;/code&gt; object with the &lt;code&gt;id&lt;/code&gt; and &lt;code&gt;value&lt;/code&gt; of the input.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We do the same thing for the &lt;code&gt;email&lt;/code&gt; and &lt;code&gt;phone&lt;/code&gt; fields. Now, when we submit any of these mini-forms, PubSub will &lt;strong&gt;create a custom event and publish it to our PubSub system.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We can now subscribe to these events anywhere in our app. And when a component publishes that event, then we can do nothing, one thing, or &lt;strong&gt;many things.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Our forms donʼt know who is listening to their events. Our subscribers donʼt know (or care) who is raising these events or why. &lt;strong&gt;We have decoupled our code.&lt;/strong&gt; The PubSub system acts as an event bus to pass around these events. And we can make up any events we need.&lt;/p&gt;

&lt;p&gt;Now, to show how it works, we will add subscribers. These will post the stringified events to the &lt;code&gt;pre&lt;/code&gt; elements we added above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;globalThis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DOMContentLoaded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// register event listeners that publish custom events&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;first&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;globalThis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_xx&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NAME_UPDATED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pre&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pre#name-output&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

          &lt;span class="nx"&gt;pre&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; 
            &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createTextNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="c1"&gt;// subscribe to EMAIL_UPDATED and&lt;/span&gt;
      &lt;span class="c1"&gt;// PHONE_UPDATED events as well&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we call the &lt;code&gt;subscribe&lt;/code&gt; function of our PubSub module. We pass it the &lt;code&gt;topic&lt;/code&gt; to which we wish to subscribe. And we pass a &lt;code&gt;callback&lt;/code&gt; function to call when a component raises that topic.&lt;/p&gt;

&lt;p&gt;In our example callback function, we grab the &lt;code&gt;pre&lt;/code&gt; element for that event. Then we stringify the event detail and append it as a child to that element.&lt;/p&gt;

&lt;p&gt;You can try this yourself on &lt;a href="https://sitebender.io/examples/pubsub-2"&gt;our example test page.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Of course, this is a simple example. We can do much more with this. For example, we can include much more detail about the event. We can also have more generic or more specific events. We could also:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allow handlers to delete themselves after a specific number of calls (e.g., once)&lt;/li&gt;
&lt;li&gt;Broadcast an event to all topics&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;BroadcastChannel&lt;/code&gt; to pass events between browser tabs or windows&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;websockets&lt;/code&gt; (or similar) to pass events between different devices&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In part 3 of this series, weʼll extend the system a bit. Stay tuned.&lt;/p&gt;

</description>
      <category>craftcode</category>
      <category>pubsub</category>
      <category>decouple</category>
      <category>vanillacode</category>
    </item>
    <item>
      <title>Adding simple PubSub, part 1</title>
      <dc:creator>Charles F. Munat</dc:creator>
      <pubDate>Wed, 13 Dec 2023 04:23:39 +0000</pubDate>
      <link>https://forem.com/craft-code/adding-simple-pubsub-part-1-4hi0</link>
      <guid>https://forem.com/craft-code/adding-simple-pubsub-part-1-4hi0</guid>
      <description>&lt;p&gt;&lt;strong&gt;PubSub is a great way to decouple components.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;4 minutes or so, 1037 words, 4th grade&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;One of the most important Craft Code principles is &lt;a href="http://localhost:3002/glossary/l#loose-coupling"&gt;loose coupling&lt;/a&gt; (or decoupling). If we organize our code into &lt;strong&gt;loosely-coupled modules&lt;/strong&gt;, then we can swap them out with ease. We can refactor them, update them, reduce tech debt.&lt;/p&gt;

&lt;p&gt;In general, the more we can &lt;strong&gt;split our code into simple modules,&lt;/strong&gt; the better. We can treat them as black boxes. But then we will need a way for them to &lt;em&gt;communicate&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;An excellent way is via an &lt;code&gt;event bus&lt;/code&gt;, or a publish-subscribe (PubSub) system. This is a tried-and-true design pattern. It has been around since, hmm, &lt;a href="https://en.wikipedia.org/wiki/Amenhotep_I"&gt;Amenhotep&lt;/a&gt; (Egyptian dude). Or thereabouts.&lt;/p&gt;

&lt;p&gt;We can build a &lt;strong&gt;simple pubsub module&lt;/strong&gt; in JavaScript. Then we can use that to connect our components to each other. And we can do it without any component having to “know” any other component. Thatʼs loose coupling.&lt;/p&gt;

&lt;p&gt;So letʼs get to it. And in successive parts, weʼll see how it all fits together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Our simple pubsub module
&lt;/h2&gt;

&lt;p&gt;First, weʼre gonna need a place to put our &lt;strong&gt;subscriptions&lt;/strong&gt;. This should work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All we need to track our subscribers is a shared object. Thus, our &lt;code&gt;subscriptions&lt;/code&gt; module exports an &lt;strong&gt;empty object literal.&lt;/strong&gt; We can share this across our other modules.&lt;/p&gt;

&lt;p&gt;Doesnʼt get much easier than that!&lt;/p&gt;

&lt;p&gt;We will add our subscribers to this object by event type and element ID. We need a &lt;code&gt;subscribe&lt;/code&gt; function to let us do that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;eventListener&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./event-listener.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;subscriptions&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./subscriptions.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;eventListener&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see from the code that we begin by importing our &lt;code&gt;subscriptions&lt;/code&gt; module. This is no more than a plain JavaScript object.&lt;/p&gt;

&lt;p&gt;Then we create &lt;strong&gt;an anonymous function&lt;/strong&gt; that we export as the &lt;code&gt;default&lt;/code&gt;. This is our &lt;code&gt;subscribe&lt;/code&gt; function. In it, we ensure that our event type exists as an object inside the &lt;code&gt;subscriptions&lt;/code&gt; object. If not, we assign an empty object literal to that key. For example, &lt;code&gt;subscriptions["click"] = {}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;OK, so are we &lt;strong&gt;already listening&lt;/strong&gt; for this event type? If we have no subscribers yet, then we can assume not. So we add an &lt;strong&gt;event listener&lt;/strong&gt; to the &lt;code&gt;body&lt;/code&gt; element listening for that type of event. We pass it an &lt;code&gt;eventListener&lt;/code&gt; function. Weʼll call this function when the user triggers that event. We discuss the &lt;code&gt;eventListener&lt;/code&gt; function below.&lt;/p&gt;

&lt;p&gt;Finally, after exiting the conditional, we set a property on the &lt;code&gt;type&lt;/code&gt; object. The key is the ID of the element weʼre watching. Our callback function is the value. Example: a “click” event on an element with ID of “my-button.” Like this: &lt;code&gt;subscriptions["click"]["my-button"] = eventListener&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What, then, does this &lt;code&gt;eventListener&lt;/code&gt; function do? Here it is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;subscriptions&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./subscriptions.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;]?.[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]?.(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a very simple function. It takes the passed &lt;code&gt;event&lt;/code&gt;. Then it extracts the ID of the &lt;code&gt;target&lt;/code&gt; element. Finally, it uses the &lt;code&gt;event.type&lt;/code&gt; and that ID to get the callback function from the &lt;code&gt;subscriptions&lt;/code&gt;. &lt;strong&gt;We call the callback function and pass it the event.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Done.&lt;/p&gt;

&lt;p&gt;Of course, weʼll also need to be able to unsubscribe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;subscriptions&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./subscriptions.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;])?.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our &lt;code&gt;unsubscribe&lt;/code&gt; function works like the &lt;code&gt;subscribe&lt;/code&gt; function but in reverse. Who knew?&lt;/p&gt;

&lt;p&gt;We import the &lt;code&gt;subscriptions&lt;/code&gt; model. Then we &lt;strong&gt;export an anonymous function&lt;/strong&gt; as &lt;code&gt;default&lt;/code&gt;. This is our &lt;code&gt;unsubscribe&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;It first ensures the the &lt;code&gt;type&lt;/code&gt; key (our event type) exists in the &lt;code&gt;subscriptions&lt;/code&gt; object. Then we use the &lt;code&gt;delete&lt;/code&gt; operator to remove the ID key from that object, if it exists. Finally, we check to see if there are &lt;strong&gt;any remaining keys&lt;/strong&gt; in that &lt;code&gt;type&lt;/code&gt; object. If not, we delete that object as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;Letʼs create a button we can use to trigger an event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"do-it"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Do it!&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rather than adding our &lt;code&gt;click&lt;/code&gt; event listener to the element itself, we can use our PubSub system. We will subscribe to &lt;code&gt;click&lt;/code&gt; events on the element with ID “do-it” instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;subscribe&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./modules/subscribe.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;do-it&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Click on do-it element.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wait … what about the pub in PubSub?
&lt;/h2&gt;

&lt;p&gt;Ah ha! Good catch. Where is our &lt;code&gt;publish&lt;/code&gt; function?&lt;/p&gt;

&lt;p&gt;Hmm. Our simple PubSub module is actually only half an implementation. What we have here is more &lt;strong&gt;event delegation.&lt;/strong&gt; It is the browser that does the publishing by raising events. All we do is to subscribe to those events.&lt;/p&gt;

&lt;p&gt;Guess what Part 2 of this essay involves.&lt;/p&gt;

&lt;p&gt;But before we go there, there are some problems with this model. Event delegation is efficient and the way to go, but &lt;strong&gt;not all events bubble up to the body.&lt;/strong&gt; Sigh … nothingʼs perfect.&lt;/p&gt;

&lt;p&gt;The two that we most want to handle are &lt;code&gt;focus&lt;/code&gt; and &lt;code&gt;blur&lt;/code&gt;. The good news is that we can use &lt;code&gt;focusin&lt;/code&gt; and &lt;code&gt;focusout&lt;/code&gt; on the body to do the same thing. But it would be nice if module users could still think &lt;code&gt;focus&lt;/code&gt; and &lt;code&gt;blur&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So we can update our functions to map one set of terms to the other. We will need a mapper function. We can extend this later as necessary.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;blur&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;focusout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;focus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;focusin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can update our &lt;code&gt;subscribe&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;castEvent&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./cast-event.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;eventListener&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./event-listener.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;subscriptions&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./subscriptions.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eventType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;castEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eventType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;eventListener&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And weʼll need to do the same to our &lt;code&gt;unsubscribe&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;castEvent&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./cast-event.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;subscriptions&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./subscriptions.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eventType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;castEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eventType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;])?.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will continue this in Part 2. Meanwhile, here is a &lt;a href="https://sitebender.io/examples/pubsub/index.html"&gt;simple, vanilla JS example.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open up the browserʼs DevTools console and then click on the “DO IT!” button. Nothing should happen. Now click the “SUBSCRIBE” button. Youʼve now subscribed to clicks on the “DO IT!” button. Click DO IT!. This time you should see a message in the console.&lt;/p&gt;

&lt;p&gt;Now click the “UNSUBSCRIBE” button. You have unsubscribed and further clicks on DO IT! will have no effect.&lt;/p&gt;

&lt;p&gt;You can also see how the castEvent mapping works. Take a look at the &lt;strong&gt;page source.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On lines #96 to #102 you can see how we subscribed to the focus and blur events on the SUBSCRIBE button. Tab to the button (or click on it) to focus it, and then tab away. You should see messages in the console to show that you focused then blurred the button.&lt;/p&gt;

&lt;p&gt;Nice, eh?&lt;/p&gt;

</description>
      <category>craftcode</category>
      <category>pubsub</category>
      <category>vanillacode</category>
      <category>decouple</category>
    </item>
    <item>
      <title>Letʼs enhance our forms</title>
      <dc:creator>Charles F. Munat</dc:creator>
      <pubDate>Mon, 04 Dec 2023 10:28:04 +0000</pubDate>
      <link>https://forem.com/craft-code/lets-enhance-our-forms-2i7p</link>
      <guid>https://forem.com/craft-code/lets-enhance-our-forms-2i7p</guid>
      <description>&lt;p&gt;&lt;strong&gt;Start with workable HTML forms, then make them better.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Less than 6 minutes, 1487 words, 4th grade&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In our &lt;a href="https://craft-code.dev/essays/code/native-form-validation-is-easy"&gt;previous article&lt;/a&gt; we showed how to create a form in HTML that validates input. And does so &lt;strong&gt;without using JavaScript or an external library.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Check out the &lt;a href="https://craft-code.dev/essays/code/native-form-validation-is-easy/example/simple-form"&gt;example form&lt;/a&gt; from that article that validates input without JS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hiding and showing the password
&lt;/h2&gt;

&lt;p&gt;Now, how can we use a bit of JavaScript to enhance the experience of our form? Well, one common enhancement is to add the ability to hide and show the password. So letʼs do that. To keep it simple, we'll strip out the other fields.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"xx-form-field"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"xx-password-field"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt;
    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"xx-field-label"&lt;/span&gt;
    &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"xx-password-input"&lt;/span&gt;
    &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"xx-password-label"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Password
  &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;br&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"xx-field-help"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"xx-password-help"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Four or more space-separated words of 4+ characters.
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
    &lt;span class="na"&gt;aria-labelledby=&lt;/span&gt;&lt;span class="s"&gt;"xx-password-help xx-password-label"&lt;/span&gt;
    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"xx-field-input xx-password-field"&lt;/span&gt;
    &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"xx-password-input"&lt;/span&gt;
    &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
    &lt;span class="na"&gt;pattern=&lt;/span&gt;&lt;span class="s"&gt;"[a-zA-Z]{4,}( [a-zA-Z]{4,}){3,}"&lt;/span&gt;
    &lt;span class="na"&gt;required=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;
    &lt;span class="na"&gt;size=&lt;/span&gt;&lt;span class="s"&gt;"36"&lt;/span&gt;
    &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are going to assume that the above is pretty self-explanatory at this point. If not, read the previous article linked above.&lt;/p&gt;

&lt;p&gt;The easiest way to do this is to add a button. When clicked, our button will toggle the password field type. Now &lt;code&gt;password&lt;/code&gt;, now &lt;code&gt;text&lt;/code&gt;. Easy peasy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;globalThis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DOMContentLoaded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#xx-password-field&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;label&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;xx-toggle-password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;show&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aria-label&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Show password.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hide&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aria-label&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hide password.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;show&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aria-label&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Show password.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Letʼs go through this step by step. Itʼs pretty simple.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We begin by adding an event listener to our &lt;code&gt;globalThis&lt;/code&gt; object to run on “DOMContentLoaded”. We pass it an anonymous arrow function that will add the enhancement to the password field.&lt;/li&gt;
&lt;li&gt;In our arrow function, we begin by setting four &lt;code&gt;const&lt;/code&gt; variables:

&lt;ol&gt;
&lt;li&gt;We use &lt;code&gt;document.querySelector&lt;/code&gt; to get the outer &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; element of our field using its ID. We assign this to “field.”&lt;/li&gt;
&lt;li&gt;Using the query selector on the field itself, we get the &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; elements by tag name. We assign them to “label” and “input” variables, respectively.&lt;/li&gt;
&lt;li&gt;Finally, we use &lt;code&gt;document.createElement("button")&lt;/code&gt; to create a &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; element. We assign it to a variable with name, “button.” Note our careful naming to make it easy to understand our code.&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;li&gt;Now we want to configure our button. As this is different from declaring and assigning variables, we leave a blank line. This makes the separation of concerns obvious. Then we:

&lt;ol&gt;
&lt;li&gt;Set the button &lt;code&gt;type&lt;/code&gt; to “button” to prevent it from submitting our form when activated. Kind of important, no?&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;button.classList.add("xx-toggle-password")&lt;/code&gt; to add a CSS class to the button. This makes it easy to style it.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;button.innerText&lt;/code&gt; to label the button, “show.”&lt;/li&gt;
&lt;li&gt;Finally, set an &lt;code&gt;aria-label&lt;/code&gt; on the button to “show password.” This makes the buttonʼs function clearer to users of screen readers.&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;li&gt;Now we have our button. We want to add an event listener to toggle the password field type when the user clicks the button. Weʼll leave another blank line. Then we will create our event handler as an anonymous arrow function. We assign it to the buttonʼs &lt;code&gt;click&lt;/code&gt; event.

&lt;ol&gt;
&lt;li&gt;We use &lt;code&gt;button.addEventListener("click", ...)&lt;/code&gt; to assign the arrow function to the buttonʼ click event.&lt;/li&gt;
&lt;li&gt;In our arrow function, we will need to handle two conditions: one when we have masked the input and one where we havenʼt. The safest way to test this is by checking the &lt;code&gt;type&lt;/code&gt; of the input. Ergo, we check that the input &lt;code&gt;type&lt;/code&gt; is “password,” &lt;code&gt;type === "password"&lt;/code&gt;, our default state.&lt;/li&gt;
&lt;li&gt;If the condition returns &lt;code&gt;true&lt;/code&gt; (&lt;code&gt;type&lt;/code&gt; is “password”), then we:

&lt;ol&gt;
&lt;li&gt;set the input &lt;code&gt;type&lt;/code&gt; to “text”,&lt;/li&gt;
&lt;li&gt;set the buttonʼs &lt;code&gt;innerText&lt;/code&gt; to “hide”,&lt;/li&gt;
&lt;li&gt;and set the &lt;code&gt;aria-label&lt;/code&gt; to “hide password”.&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;li&gt;Then we return from the function. The password should now be visible and the button should say, “HIDE.”&lt;/li&gt;
&lt;li&gt;If the condition fails, i.e., the input &lt;code&gt;type&lt;/code&gt; &lt;strong&gt;isnʼt&lt;/strong&gt; “password,” then we have unmasked the field. So we:

&lt;ol&gt;
&lt;li&gt;set the &lt;code&gt;type&lt;/code&gt; back to “password”,&lt;/li&gt;
&lt;li&gt;set the buttonʼs &lt;code&gt;innerText&lt;/code&gt; back to “show”,&lt;/li&gt;
&lt;li&gt;and of course, our &lt;code&gt;aria-label&lt;/code&gt; back to “show password.”
And thereʼs our toggle.&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Last but not least, we add our button to the form with &lt;code&gt;label.appendChild(button)&lt;/code&gt;. As a screen reader reads our label aloud, so, too, will it read the buttonʼs &lt;code&gt;aria-label&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As we say, easy peasy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not a component library?
&lt;/h2&gt;

&lt;p&gt;“But … but … but …” we can hear some readers say, “why not just use a pre-coded field from a popular component library?”&lt;/p&gt;

&lt;p&gt;And, “why would we want to have to write this code every time we wanted an enhanced password field?”&lt;/p&gt;

&lt;p&gt;Why on Earth would we do that? Or on Mars, for that matter?&lt;/p&gt;

&lt;p&gt;I put this field together &lt;strong&gt;once&lt;/strong&gt;. I use Astro for its bundling benefits: it makes a component architecture easy. I have no need for its “islands” architecture. So, I have been building my own component library bit by bit (pun intended).&lt;/p&gt;

&lt;p&gt;Although I shun passwords as much as practicable, it pays to have a field like this. &lt;strong&gt;So I added a PasswordField to my Astro component library.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And I included a prop called &lt;code&gt;allowShow&lt;/code&gt;. When it is &lt;code&gt;true&lt;/code&gt;, then the component includes the JS enhance script and features a “SHOW” button. When it is &lt;code&gt;false&lt;/code&gt; or missing, the component does not.&lt;/p&gt;

&lt;p&gt;Easy peasy, as I may have said once or twice before. And the benefit is obvious:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I know every line of the code.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I wrote it, and the responsibility for making sure it is bug-free, accessible, user-friendly, etc. is &lt;strong&gt;on me.&lt;/strong&gt; I am not counting on devs I have never met to keep my codebase current, robust, and secure.&lt;/p&gt;

&lt;p&gt;And because it is an ad hoc component — written specifically to fill my needs — rather than a highly-abstracted, one-size-fits-all attempt to please everyone, it can be &lt;strong&gt;small and efficient.&lt;/strong&gt; No superfluous parts. William of Ockham would be proud.&lt;/p&gt;

&lt;p&gt;Case in point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"MuiFormControl-root css-kzv9dm"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt;
    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-sizeMedium MuiInputLabel-filled MuiFormLabel-colorPrimary MuiFormLabel-filled MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-sizeMedium MuiInputLabel-filled css-1rgmeur"&lt;/span&gt;
    &lt;span class="na"&gt;data-shrink=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;
    &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"filled-adornment-password"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Password
  &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"MuiInputBase-root MuiFilledInput-root MuiFilledInput-underline MuiInputBase-colorPrimary MuiInputBase-formControl MuiInputBase-adornedEnd css-1thjcug"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
      &lt;span class="na"&gt;aria-invalid=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;
      &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"filled-adornment-password"&lt;/span&gt;
      &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
      &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"MuiInputBase-input MuiFilledInput-input MuiInputBase-inputAdornedEnd css-ftr4jk"&lt;/span&gt;
      &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;
    &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"MuiInputAdornment-root MuiInputAdornment-positionEnd MuiInputAdornment-filled MuiInputAdornment-sizeMedium css-1mzf9i9"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt;
        &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"MuiButtonBase-root MuiIconButton-root MuiIconButton-edgeEnd MuiIconButton-sizeMedium css-slyssw"&lt;/span&gt;
        &lt;span class="na"&gt;tabindex=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
        &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;
        &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"toggle password visibility"&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt;
          &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"MuiSvgIcon-root MuiSvgIcon-fontSizeMedium css-vubbuv"&lt;/span&gt;
          &lt;span class="na"&gt;focusable=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;
          &lt;span class="na"&gt;aria-hidden=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;
          &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 24 24"&lt;/span&gt;
          &lt;span class="na"&gt;data-testid=&lt;/span&gt;&lt;span class="s"&gt;"VisibilityIcon"&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt;
            &lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;"M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;gt;&amp;lt;/path&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"MuiTouchRipple-root css-w0pj6f"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To be fair, the MUI folks have improved their code in many ways over the past several years. Now they use the proper &lt;code&gt;data-&lt;/code&gt; attribute format. They have discovered &lt;code&gt;aria-&lt;/code&gt; attributes as well.&lt;/p&gt;

&lt;p&gt;They even have an &lt;code&gt;aria-label&lt;/code&gt; attribute. That said, “toggle password visibility” is wordy and fails to make clear the current state of the field. Is it unmasked or masked?&lt;/p&gt;

&lt;p&gt;There remain several UX problems, but weʼll save those for another article.&lt;/p&gt;

&lt;p&gt;What is most noticeable about the above code is &lt;strong&gt;the excessive number of CSS class names.&lt;/strong&gt; Why on Earth do we need so many?&lt;/p&gt;

&lt;p&gt;Well, here is the root of the problem. MUI must &lt;strong&gt;abstract beyond belief&lt;/strong&gt; so that anyone can use it for anything. This is not an MUI problem; it is a &lt;em&gt;component library&lt;/em&gt; problem.&lt;/p&gt;

&lt;p&gt;The irony is that MUI was once based on &lt;a href="https://m3.material.io/"&gt;Material Design&lt;/a&gt;, and still is to some degree. But they have wrangled it to the point that you could make it look like an old Windows 95 form if you wanted to.&lt;/p&gt;

&lt;p&gt;But why? My sites each have a specific look and feel. I have a design system for each. I do not need an enormous stylesheet and a million classnames to make them look and work in different ways. Ways that I will never need.&lt;/p&gt;

&lt;p&gt;I give each relevant HTML element one class — maybe two. Then I write my stylesheet to &lt;strong&gt;set the specific style for my design system.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  It gets worse
&lt;/h2&gt;

&lt;p&gt;Oh, but thereʼs more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where is the CSS for this field? Where is the JavaScript?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Why, they have bundled them away somewhere else. Good luck finding them. Good luck changing them.&lt;/p&gt;

&lt;p&gt;Instead, MUI provides a complex system for &lt;strong&gt;configuring&lt;/strong&gt; their components. Hey, you are no longer a &lt;em&gt;programmer&lt;/em&gt; or even a &lt;em&gt;developer&lt;/em&gt;. You are now a &lt;strong&gt;configurer.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Fun, right?&lt;/p&gt;

&lt;p&gt;If you want this field to work some way that the MUI folks didnʼt allow for, then good luck. I have spent many painful hours trying to force MUI to fit some designerʼs style preference. Oh, the PTSD!&lt;/p&gt;

&lt;p&gt;But when we code the field ourselves with simple HTML, CSS, and JavaScript, then &lt;strong&gt;it is all right there.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A recent fad — devs can never leave well enough alone — is something called LoB, Locality of Behavior. LoB states that the behavior of a unit of code should be as obvious as possible by looking only at that unit of code.&lt;/p&gt;

&lt;p&gt;I guess multiple screens and the ability to put several files side-by-side was the wrong path. Silly me.&lt;/p&gt;

&lt;p&gt;But with my Astro component model, I could, if I wanted to (I donʼt), create a &lt;strong&gt;Single-File Component&lt;/strong&gt; (SFC). That would put the CSS, JS, and HTML all in the same “PasswordField.astro” file.&lt;/p&gt;

&lt;p&gt;Me? I prefer to keep structure/semantics, style/layout, and behavior separate. In separate files. And with roughly 10ha of screen real estate at my workstation, I can refer to side-by-side files quickly as I work.&lt;/p&gt;

&lt;p&gt;So my Astro PasswordField component has a folder called “PasswordField” and three files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;PasswordField/&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;index.astro&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;index.css&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;index.ts&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I import the CSS and TypeScript files at the top of my Astro file. Astro builds the component, transpiles the TS to JS, and bundles and dedupes all the CSS and JS.&lt;/p&gt;

&lt;p&gt;We can do a lot more with simple JavaScript enhancements. But it might surprise you how little we need to do so.&lt;/p&gt;

&lt;p&gt;In a coming article, we will show how many HTML form components are already quite enhanced enough.&lt;/p&gt;

</description>
      <category>html</category>
      <category>forms</category>
      <category>progressiveenhancement</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Native form validation is easy</title>
      <dc:creator>Charles F. Munat</dc:creator>
      <pubDate>Wed, 29 Nov 2023 01:44:14 +0000</pubDate>
      <link>https://forem.com/craft-code/native-form-validation-is-easy-jg5</link>
      <guid>https://forem.com/craft-code/native-form-validation-is-easy-jg5</guid>
      <description>&lt;p&gt;&lt;strong&gt;You can validate your forms without JavaScript.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Less than 6 minutes, 1470 words, 4th grade&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It is easy to &lt;strong&gt;create web forms that validate themselves without JavaScript.&lt;/strong&gt; Why so many developers ignore this native capability is an open question.&lt;/p&gt;

&lt;p&gt;I guess that when all you have is React or similar, then everything looks like a React component.&lt;/p&gt;

&lt;p&gt;But what happens when the user disables JavaScript (or it is otherwise unavailable)? OK, you are rendering your React components on the server. Nice. But what happens to your client-side validation?&lt;/p&gt;

&lt;p&gt;But before we get into this, a brief digression. Letʼs take another look at our previous article, &lt;a href="https://dev.to/craft-code/progressive-enhancement-31ii"&gt;Progressive enhancement&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;requestAnimationFrame&lt;/code&gt; option
&lt;/h2&gt;

&lt;p&gt;After I published that article, I got a suggestion from a good friend. He recommended that I animate the accordion elements with &lt;code&gt;requestAnimationFrame&lt;/code&gt;. So I tried it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;toggleAccordion&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accordion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;closest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;details&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;accordion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.xx-accordion-content&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;openHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;xxOpenHeight&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accordion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;shutAccordion&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;stopHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;decrement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px`&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;stopHeight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;accordion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

          &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nf"&gt;shutAccordion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;decrement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;stopHeight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;decrement&lt;/span&gt;
          &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;shutAccordion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;openHeight&lt;/span&gt;&lt;span class="p"&gt;)()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;openAccordion&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;stopHeight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;increment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px`&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;stopHeight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;openAccordion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;stopHeight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;increment&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;accordion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="nf"&gt;openAccordion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;openHeight&lt;/span&gt;&lt;span class="p"&gt;)()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I wonʼt go into a lot of detail here as it is pretty self-explanatory. We create open and close &lt;strong&gt;outer functions&lt;/strong&gt; that return an &lt;strong&gt;inner closure.&lt;/strong&gt; &lt;code&gt;requestAnimationFrame&lt;/code&gt; calls that closure recursively. In this way, we can &lt;strong&gt;increment or decrement the height on each frame&lt;/strong&gt; immutably.&lt;/p&gt;

&lt;p&gt;Is this a potential performance bottleneck, creating all those closures? Maybe. But itʼs the simplest way to do it, and we donʼt &lt;a href="https://craft-code.dev/glossary/p#premature-optimization"&gt;prematurely optimize&lt;/a&gt; here at Craft Code. If it turns out to be a bottleneck, weʼll switch to a loop and a mutable variable.&lt;/p&gt;

&lt;p&gt;Betcha it works just fine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But which is better?&lt;/strong&gt; &lt;code&gt;setTimeout&lt;/code&gt;? Or &lt;code&gt;requestAnimationFrame&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;Itʼs a pretty close call. &lt;code&gt;setTimeout&lt;/code&gt; is older and will work in browsers such as Internet Explorer pre-v10. Of course we can polyfill that.&lt;/p&gt;

&lt;p&gt;An argument for &lt;code&gt;requestAnimationFrame&lt;/code&gt; is that it may be smoother. Especially if there are many simultaneous animations on the page. It also may be more performant. Donʼt need to support very old browsers? Then &lt;code&gt;requestAnimationFrame&lt;/code&gt; is probably the better way to go.&lt;/p&gt;

&lt;p&gt;You should ask, “Where in the world are my users? What browsers are they using?” That will help you to determine which option is best. But then we should always start with that question, right?&lt;/p&gt;

&lt;p&gt;Try it on our &lt;a href="http://localhost:3002/essays/code/native-form-validation-is-easy/example/animation-frame"&gt;example animation frame accordion.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  About those forms
&lt;/h2&gt;

&lt;p&gt;Letʼs get to our forms. A few simple recommendations to start.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Donʼt use placeholders. Just donʼt. &lt;strong&gt;Use &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; instead.&lt;/strong&gt; Set the &lt;code&gt;for&lt;/code&gt; attribute to the &lt;code&gt;id&lt;/code&gt; of the input labelled.&lt;/li&gt;
&lt;li&gt;Group related controls in &lt;code&gt;&amp;lt;fieldset&amp;gt;&lt;/code&gt; elements. Use the &lt;code&gt;&amp;lt;legend&amp;gt;&lt;/code&gt; element to label the group. Fieldsets are easily styled with CSS these days.&lt;/li&gt;
&lt;li&gt;Ensure that your form elements are keyboard navigable. And in the same order that they appear visually. &lt;strong&gt;Donʼt confuse your users.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;You can best determine other considerations, such as where to put buttons or which buttons to use, with user testing. &lt;strong&gt;There is no one right way.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With those caveats in mind, let us begin with a simple form. Here is one with which we are all familiar … but not for much longer, we hope.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt;
  &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"xx-form"&lt;/span&gt;
  &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"GET"&lt;/span&gt;
  &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"simple-form"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;fieldset&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"xx-fieldset"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;legend&amp;gt;&lt;/span&gt;Please sign in&lt;span class="nt"&gt;&amp;lt;/legend&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"xx-form-field"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt;
        &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"xx-field-label"&lt;/span&gt;
        &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
        &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"xx-email-label"&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        Email address
      &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;br&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"xx-field-help"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"xx-email-help"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        The email address with which you signed up.
      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
        &lt;span class="na"&gt;aria-labelledby=&lt;/span&gt;&lt;span class="s"&gt;"xx-email-help xx-email-label"&lt;/span&gt;
        &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"xx-field-input xx-email-field"&lt;/span&gt;
        &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
        &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;
        &lt;span class="na"&gt;size=&lt;/span&gt;&lt;span class="s"&gt;"36"&lt;/span&gt;
        &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"xx-form-field"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt;
        &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"xx-field-label"&lt;/span&gt;
        &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
        &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"xx-password-label"&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        Password
      &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;br&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"xx-field-help"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"xx-password-help"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        Four or more space-separated words of 4+ characters.
      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
        &lt;span class="na"&gt;aria-labelledby=&lt;/span&gt;&lt;span class="s"&gt;"xx-password-help xx-password-label"&lt;/span&gt;
        &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"xx-field-input xx-password-field"&lt;/span&gt;
        &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
        &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
        &lt;span class="na"&gt;pattern=&lt;/span&gt;&lt;span class="s"&gt;"[a-zA-Z]{4,}( [a-zA-Z]{4,}){3,}"&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;
        &lt;span class="na"&gt;size=&lt;/span&gt;&lt;span class="s"&gt;"36"&lt;/span&gt;
        &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/fieldset&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt;
    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"xx-submit-button"&lt;/span&gt;
    &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Sign in
  &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As in previous examples, we use CSS class names on all our elements. The &lt;code&gt;xx-&lt;/code&gt; is a &lt;strong&gt;namespace&lt;/strong&gt;, i.e., &lt;code&gt;cc-&lt;/code&gt; for Craft Code. This allows us to select our elements in our CSS and avoids collisions.&lt;/p&gt;

&lt;p&gt;We like grouping controls in fieldsets, and using the &lt;code&gt;legend&lt;/code&gt; for the title of our form. We group our labels and inputs in &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; elements with the class name, &lt;code&gt;xx-form-field&lt;/code&gt;. This permits us to style them as a group.&lt;/p&gt;

&lt;p&gt;Our view is that best practice is to &lt;strong&gt;put the label above the input.&lt;/strong&gt; To make this work even when the user disables CSS, we use a &lt;code&gt;&amp;lt;br&amp;gt;&lt;/code&gt; element. Note that we &lt;strong&gt;tie our labels to their inputs&lt;/strong&gt; with the &lt;code&gt;for&lt;/code&gt; attribute.&lt;/p&gt;

&lt;p&gt;Whenever possible, we choose our HTML elements to take advantage of browser features. We chose an &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; of type &lt;code&gt;email&lt;/code&gt; rather than type &lt;code&gt;text&lt;/code&gt;. Because of this, the browser will validate the value of the input on submit.&lt;/p&gt;

&lt;p&gt;If the value is &lt;strong&gt;not a potentially valid email address&lt;/strong&gt;, then submission fails.&lt;/p&gt;

&lt;p&gt;We also set the &lt;code&gt;required&lt;/code&gt; attribute on the input. The form will &lt;strong&gt;require&lt;/strong&gt; a valid email address before we can submit it.&lt;/p&gt;

&lt;p&gt;One down.&lt;/p&gt;

&lt;p&gt;Then for the password field, we choose the &lt;code&gt;password&lt;/code&gt; type, which &lt;strong&gt;masks the characters&lt;/strong&gt; as we type them. We also set the &lt;code&gt;required&lt;/code&gt; attribute so the user must provide a password. We could use &lt;code&gt;minlength&lt;/code&gt; to set a minimum length for the password, but we have a better idea.&lt;/p&gt;

&lt;p&gt;As &lt;a href="https://xkcd.com/936/"&gt;this brilliant xkcd comic&lt;/a&gt; makes clear, a better approach to passwords is to use four random words. To this end, we have used the &lt;code&gt;pattern&lt;/code&gt; attribute on the password input. It requires the password to be four or more space-separated words of four or more characters each.&lt;/p&gt;

&lt;p&gt;The pattern: &lt;code&gt;[a-zA-Z]{4,}( [a-zA-Z]{4,}){3,}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Of course, we make this requirement clear with a help message above the input. There a screen reader will announce it &lt;em&gt;before&lt;/em&gt; entering the field. And to be extra certain, we associate it with the input via the &lt;code&gt;aria-labelledby&lt;/code&gt; and &lt;code&gt;id&lt;/code&gt; attributes as shown.&lt;/p&gt;

&lt;p&gt;Hereʼs what that looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr6oe9pajzggp76z25nal.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr6oe9pajzggp76z25nal.png" alt="Submitting the form with a bad email address raises the message, “Please include an '@' in the email address.”" width="800" height="233"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here are the possibilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Empty input&lt;/strong&gt; (required): “Please fill out this field.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;bob@:&lt;/strong&gt; “Please enter a part following '@'. 'bob@' is incomplete.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;@dobbs:&lt;/strong&gt; “Please enter a part followed by '@'. '@dobbs' is incomplete.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;bob.dobbs:&lt;/strong&gt; “Please include an '@' in the email address. 'bob.dobbs' is missing an '@'.” (As above.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is what happens if the password doesnʼt match our pattern:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpqhk9z9npirfby9hztyq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpqhk9z9npirfby9hztyq.png" alt="Submitting the form with a bad password raises the message, “Please match the requested format.”" width="800" height="282"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here are the possibilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Empty input&lt;/strong&gt; (required): “Please fill out this field.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bob is yer uncle:&lt;/strong&gt; “Please match the requested format.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Be sure that you explain what the “requested format” is!&lt;/strong&gt; Donʼt make your users guess!&lt;/p&gt;

&lt;p&gt;Try “correct horse battery staple”. Does it work? xkcd would be proud.&lt;/p&gt;

&lt;p&gt;Give this &lt;a href="http://localhost:3002/essays/code/native-form-validation-is-easy/example/simple-form"&gt;simple form&lt;/a&gt; a try.&lt;/p&gt;

&lt;h2&gt;
  
  
  What else can we validate?
&lt;/h2&gt;

&lt;p&gt;Different types of input provide different validations. Here are some examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pattern mismatch:&lt;/strong&gt; You can use the &lt;code&gt;pattern&lt;/code&gt; attribute with other input types as well. These include: &lt;code&gt;email&lt;/code&gt;, &lt;code&gt;search&lt;/code&gt;, &lt;code&gt;tel&lt;/code&gt;, &lt;code&gt;text&lt;/code&gt;, and &lt;code&gt;url&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Range overflow/underflow:&lt;/strong&gt; You can set &lt;code&gt;max&lt;/code&gt; and &lt;code&gt;min&lt;/code&gt; values on types &lt;code&gt;date&lt;/code&gt;, &lt;code&gt;datetime-local&lt;/code&gt;, &lt;code&gt;month&lt;/code&gt;, &lt;code&gt;number&lt;/code&gt;, &lt;code&gt;range&lt;/code&gt;, &lt;code&gt;time&lt;/code&gt;, and &lt;code&gt;week&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step mismatch:&lt;/strong&gt; You can set the &lt;code&gt;step&lt;/code&gt; value (a number) on types &lt;code&gt;date&lt;/code&gt;, &lt;code&gt;datetime-local&lt;/code&gt;, &lt;code&gt;month&lt;/code&gt;, &lt;code&gt;number&lt;/code&gt;, &lt;code&gt;range&lt;/code&gt;, &lt;code&gt;time&lt;/code&gt;, and &lt;code&gt;week&lt;/code&gt;. Sheesh! Who knew there were so many input types? MDN provides a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/step"&gt;handy list of default values.&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Too long/short:&lt;/strong&gt; You can set a &lt;code&gt;maxlength&lt;/code&gt; and/or &lt;code&gt;minlength&lt;/code&gt; (as a number of characters) on types &lt;code&gt;email&lt;/code&gt;, &lt;code&gt;password&lt;/code&gt;, &lt;code&gt;search&lt;/code&gt;, &lt;code&gt;tel&lt;/code&gt;, &lt;code&gt;text&lt;/code&gt;, and &lt;code&gt;url&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Donʼt try to memorize these. Remember: code (and learn) &lt;strong&gt;just in time.&lt;/strong&gt; There is no point in wasting time and effort that you may never need.&lt;/p&gt;

&lt;p&gt;Instead, first design your form. Then determine what needs validation. Finally, refer to &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/ValidityState"&gt;Mozilla Developer Network&lt;/a&gt; or equivalent to see what fits your needs.&lt;/p&gt;

&lt;p&gt;For example, you might create an &lt;strong&gt;integer input&lt;/strong&gt; like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
  &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"iq"&lt;/span&gt;
  &lt;span class="na"&gt;max=&lt;/span&gt;&lt;span class="s"&gt;"210"&lt;/span&gt;
  &lt;span class="na"&gt;min=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
  &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"iq"&lt;/span&gt;
  &lt;span class="na"&gt;required&lt;/span&gt;
  &lt;span class="na"&gt;step=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;
  &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"number"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This accepts only &lt;strong&gt;positive integers&lt;/strong&gt; between 0 and 210 (the highest IQ on record). The &lt;code&gt;step&lt;/code&gt; value of &lt;code&gt;1&lt;/code&gt; does not prevent entering decimals, such as 100.1. But try submitting that. Youʼll get a warning:&lt;/p&gt;

&lt;p&gt;“Please enter a valid value. The two nearest valid values are 100 and 101.”&lt;/p&gt;

&lt;p&gt;You can try it on our &lt;a href="https://craft-code.dev/essays/code/native-form-validation-is-easy/example/test-inputs"&gt;example form&lt;/a&gt;. What happens if you enter -50? What about 300?&lt;/p&gt;

&lt;p&gt;You could also do this with an input of type &lt;code&gt;text&lt;/code&gt; by setting the &lt;code&gt;pattern&lt;/code&gt; attribute to &lt;code&gt;[0-9]*&lt;/code&gt;. Or use &lt;code&gt;([0-9]|[1-9][0-9]*)&lt;/code&gt;? if you want to disallow starting zeros. But either way you lose the semantic value of the &lt;code&gt;number&lt;/code&gt; type.&lt;/p&gt;

&lt;p&gt;Our recommendation: stick with the &lt;code&gt;number&lt;/code&gt; input.&lt;/p&gt;

&lt;p&gt;The key takeaway here is this: &lt;strong&gt;good enough is, by definition, good enough.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Too often, designers and devs create &lt;strong&gt;bloated, ugly, brittle, incomprehensible code.&lt;/strong&gt; We do it because we want to tweak our interface in some minor way, but plain HTML doesnʼt make it easy. So we hack the heck out of it.&lt;/p&gt;

&lt;p&gt;The temptation to throw out all the benefits of semantic, accessible, browser-native code just to make it a bit slicker can be overwhelming. But &lt;strong&gt;resist, resist, resist!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This isnʼt about UX. Your users donʼt care about your flashy interface, no matter what they tell you when you ask. Thatʼs your ego talking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Users care about usability, findability, comprehensibility, accessibility.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Can they find what they are looking for? Can they understand it when they find it? Can they make your site do what they want it to do?&lt;/p&gt;

&lt;p&gt;And we can manage all that with an elegant design without having to &lt;strong&gt;hack&lt;/strong&gt; the code. Simple and beautiful. And more stable, less likely to be buggy, and be easier to code and refactor, too.&lt;/p&gt;

&lt;p&gt;And if they never see your flashy, edgy new design, they will never miss it.&lt;/p&gt;

</description>
      <category>validation</category>
      <category>forms</category>
      <category>webdev</category>
      <category>dom</category>
    </item>
    <item>
      <title>Progressive enhancement</title>
      <dc:creator>Charles F. Munat</dc:creator>
      <pubDate>Mon, 20 Nov 2023 09:31:36 +0000</pubDate>
      <link>https://forem.com/craft-code/progressive-enhancement-31ii</link>
      <guid>https://forem.com/craft-code/progressive-enhancement-31ii</guid>
      <description>&lt;p&gt;8-9 minutes, 2161 words, 5th grade&lt;/p&gt;

&lt;p&gt;Now that weʼve seen how semantic HTML alone produces a &lt;a href="https://dev.to/craft-code/reponsive-by-default-mjb"&gt;fully-responsive web page&lt;/a&gt;, where do we go next?&lt;/p&gt;

&lt;p&gt;Our &lt;a href="https://craft-code.dev/essays/code/responsive-by-default/example"&gt;plain HTML page&lt;/a&gt; is ugly as sin, so weʼre gonna need some CSS to spiff it up. But today letʼs keep the CSS simple. Weʼll return to good CSS practices soon in a follow-up article.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Progressive enhancement&lt;/strong&gt; starts with something that is already functional and usable. Then it adds just enough JavaScript to enhance that functionality.&lt;/p&gt;

&lt;p&gt;In this essay weʼll take a simple &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; element, which will work in any browser, and make it into an accordion. The &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; element with its &lt;code&gt;&amp;lt;summary&amp;gt;&lt;/code&gt; is already an accordion. But it doesnʼt look like one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the HTML
&lt;/h2&gt;

&lt;p&gt;To begin, letʼs start with some essential HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;details&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"xx-accordion"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;summary&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"xx-accordion-summary"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Always visible&lt;span class="nt"&gt;&amp;lt;/summary&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"xx-accordion-content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Hidden content&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;More hidden content&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/details&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see an &lt;a href="https://craft-code.dev/essays/code/progressive-enhancement/html-only.html"&gt;example page here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;No doubt you have spotted the &lt;code&gt;class&lt;/code&gt; attributes. We prefer to &lt;strong&gt;apply CSS by class&lt;/strong&gt;. We can fall back to &lt;em&gt;class-and-tag-name&lt;/em&gt; if we need more specificity, as youʼll see below.&lt;/p&gt;

&lt;p&gt;So whatʼs up with the “xx-” prefix?&lt;/p&gt;

&lt;p&gt;Simple. To avoid &lt;strong&gt;name clashes&lt;/strong&gt; with imported CSS, we namespace our classes. For example, the &lt;a href="https://craft-code.dev/"&gt;Craft Code site&lt;/a&gt; might use “cc-” as in &lt;code&gt;cc-accordion&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding the initial CSS
&lt;/h2&gt;

&lt;p&gt;Letʼs add a bit of CSS to give it that “accordion” look and feel. Here we go:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.xx-accordion&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#fafafa&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#747481&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#2c2c30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.xx-accordion-summary&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#0d4872&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#fafafa&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.25rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.xx-accordion-content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#747481&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, we provide &lt;a href="https://craft-code.dev/essays/code/progressive-enhancement/basic-css.html"&gt;an example page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is already a workable accordion component. But most accordions have &lt;strong&gt;more than one expandable element&lt;/strong&gt;. So letʼs add a few more elements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"xx-accordion-group"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;details&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"xx-accordion"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;summary&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"xx-accordion-summary"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      Katherine Johnson
    &lt;span class="nt"&gt;&amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"xx-accordion-content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt;
          &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://en.wikipedia.org/wiki/Katherine_Johnson"&lt;/span&gt;
          &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"external"&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Creola Katherine Johnson&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt; was an American
        mathematician whose calculations of orbital mechanics
        as a NASA employee were critical to the success of
        the first and subsequent U.S. crewed spaceflights.
      &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/details&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;details&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"xx-accordion"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;summary&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"xx-accordion-summary"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      Dorothy Vaughan
    &lt;span class="nt"&gt;&amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"xx-accordion-content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt;
          &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://en.wikipedia.org/wiki/Dorothy_Vaughan"&lt;/span&gt;
          &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"external"&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Dorothy Jean Johnson Vaughan&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt; was an American
        mathematician  and human computer who worked for the
        National Advisory Committee for Aeronautics (NACA), and
        NASA, at Langley Research Center in Hampton, Virginia.
      &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/details&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;details&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"xx-accordion"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;summary&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"xx-accordion-summary"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      Mary Jackson
    &lt;span class="nt"&gt;&amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"xx-accordion-content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt;
          &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://en.wikipedia.org/wiki/Mary_Jackson_(engineer)"&lt;/span&gt;
          &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"external"&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Mary Jackson&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt; was an American mathematician and
        aerospace engineer at the National Advisory Committee
        for Aeronautics (NACA), which in 1958 was succeeded by
        the National Aeronautics and Space Administration (NASA).
      &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/details&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have also adjusted the CSS a bit to make it work with multiple elements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.xx-accordion-group&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#0d4872&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.xx-accordion&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#fafafa&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#2c2c30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.xx-accordion-summary&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#0d4872&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#fafafa&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#fafafa&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.25rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.xx-accordion&lt;/span&gt;&lt;span class="nd"&gt;:last-child&lt;/span&gt; &lt;span class="nt"&gt;summary&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.xx-accordion-content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;We added the &lt;code&gt;.xx-accordion-group&lt;/code&gt; properties. And moved the outer border from the individual elements to the group. We also changed the border color to match the &lt;code&gt;&amp;lt;summary&amp;gt;&lt;/code&gt; background color.&lt;/li&gt;
&lt;li&gt;We removed the top border from the content block. Then we replaced it with a bottom border on the &lt;code&gt;&amp;lt;summary&amp;gt;&lt;/code&gt; element. We also changed the color to the background color of the &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; elements. This makes it appear as a horizontal rule between the accordion elements.&lt;/li&gt;
&lt;li&gt;We donʼt want that bottom border on the last element in the accordion group. So we used &lt;code&gt;.xx-accordion:last-child summary&lt;/code&gt; to remove that one bottom border.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the &lt;a href="https://craft-code.dev/essays/code/progressive-enhancement/accordion-group.html"&gt;full accordion example&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enhancing our accordion
&lt;/h2&gt;

&lt;p&gt;Rats! The accordion elements open and close instantly when you click on the summary. It would be nice if we animated the open and close.&lt;/p&gt;

&lt;p&gt;We bet that your first thought will be that we can do this with &lt;strong&gt;CSS animations&lt;/strong&gt;. We thought so, too. But it turns out that it is fiendishly difficult to make that work. By that we mean that we couldnʼt. Sigh.&lt;/p&gt;

&lt;p&gt;CSS should always be your &lt;strong&gt;first choice for effects&lt;/strong&gt;, but when CSS fails you, JavaScript steps up. We can do it the way we used to.&lt;/p&gt;

&lt;h2&gt;
  
  
  CSS transitions for the loss
&lt;/h2&gt;

&lt;p&gt;Naturally, the first thing we tried was a &lt;strong&gt;CSS transition&lt;/strong&gt; on the height of the content block. But this only works if you know the &lt;strong&gt;height of the block&lt;/strong&gt; in advance. It wonʼt work (for us, anyway) with height set to &lt;code&gt;auto&lt;/code&gt; or &lt;code&gt;fit-content&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;OK, fine! Instead we set the open height to a CSS property with a fallback to auto: &lt;code&gt;var(--xx-accordion-height, auto)&lt;/code&gt;. That oughta do it.&lt;/p&gt;

&lt;p&gt;Now we need to set that property for each accordion element separately. Enter &lt;strong&gt;JavaScript&lt;/strong&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  JavaScript to the rescue
&lt;/h2&gt;

&lt;p&gt;The first thing we want to do is write a &lt;strong&gt;function&lt;/strong&gt; that grabs any and all accordion elements on the page. Then weʼll add an &lt;strong&gt;event listener&lt;/strong&gt; to each of them. And we want our function to run once as soon as the &lt;strong&gt;DOM&lt;/strong&gt; has finished loading.&lt;/p&gt;

&lt;p&gt;Here, then, is our first pass at the JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;enhanceAccordions&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nodes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.xx-accordion-content&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="s2"&gt;`--xx-accordion-height: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientHeight&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px`&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;globalThis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DOMContentLoaded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;enhanceAccordions&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And our modified CSS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.xx-accordion-content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;overflow-y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="m"&gt;0.5s&lt;/span&gt; &lt;span class="n"&gt;ease-in-out&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.xx-accordion&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;open&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nc"&gt;.xx-accordion-content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--xx-accordion-height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="m"&gt;0.5s&lt;/span&gt; &lt;span class="n"&gt;ease-in-out&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And it does work. Beautifully … the &lt;em&gt;first&lt;/em&gt; time we open an accordion element. And then it stops working. And it doesnʼt animate the close at all. Why not?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Because toggling the &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; element sets “open” to &lt;code&gt;false&lt;/code&gt; and thatʼs that.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Slam.&lt;/p&gt;

&lt;p&gt;OK, fine. Time to show it whoʼs boss.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kicking butt and taking names
&lt;/h2&gt;

&lt;p&gt;First, letʼs &lt;strong&gt;delete the CSS&lt;/strong&gt; we added. Forget using a transition! Weʼll do this the old-fashioned way.&lt;/p&gt;

&lt;p&gt;First issue: when the &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; element toggles shut, it closes the accordion abruptly. We need to prevent that. To do so, we will seize control.&lt;/p&gt;

&lt;p&gt;Instead of handling the &lt;code&gt;toggle&lt;/code&gt; event, letʼs capture the &lt;code&gt;click&lt;/code&gt; on the &lt;code&gt;&amp;lt;summary&amp;gt;&lt;/code&gt; element. Then we will &lt;strong&gt;prevent the click from bubbling up&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We still need to know the height of the open accordion, but we donʼt need it in our CSS. So letʼs use a &lt;strong&gt;property&lt;/strong&gt; instead. Weʼll call it &lt;code&gt;xxOpenHeight&lt;/code&gt;. (Do remember to replace &lt;code&gt;xx&lt;/code&gt; with your own namespace.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;toggleAccordion&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;xxOpenHeight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;enhanceAccordions&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nodes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.xx-accordion&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;summary&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.xx-accordion-content&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;xxOpenHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientHeight&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toggleAccordion&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;globalThis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DOMContentLoaded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;enhanceAccordions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check out the &lt;a href="https://craft-code.dev/essays/code/progressive-enhancement/accordion-group.html"&gt;enhanced example&lt;/a&gt; and open the browser console. Note how clicking on the a summary prints the height of that accordionʼs content block to the console. Weʼre off to a good start.&lt;/p&gt;

&lt;p&gt;Now letʼs complete the &lt;code&gt;toggleAccordion&lt;/code&gt; function to &lt;strong&gt;animate the accordion elements&lt;/strong&gt;. Weʼll explain this code line by line below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;toggleAccordion&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accordion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;closest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;details&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;accordion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.xx-accordion-content&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;openHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;xxOpenHeight&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accordion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;openHeight&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px`&lt;/span&gt;

    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;shutAccordion&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;maxHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxHeight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;maxHeight&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;maxHeight&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;
          &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
          &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;maxHeight&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px`&lt;/span&gt;

        &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shutAccordion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nx"&gt;accordion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;shutAccordion&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0px&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;openAccordion&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;maxHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxHeight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nx"&gt;accordion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;maxHeight&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;openHeight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;maxHeight&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px`&lt;/span&gt;

      &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;openAccordion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;openAccordion&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;We need to prevent the element from &lt;strong&gt;toggling&lt;/strong&gt;. We can do this with &lt;code&gt;event.preventDefault()&lt;/code&gt; on line #2 above. Now we are responsible for opening and closing the accordion ourselves.&lt;/li&gt;
&lt;li&gt;Next, we use the &lt;code&gt;event.target&lt;/code&gt; to get the &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt;(accordion) element, our content block, and the &lt;code&gt;xxOpenHeight&lt;/code&gt;. Lines #4-7.&lt;/li&gt;
&lt;li&gt;Now we have two possibilities. Either the accordion is &lt;strong&gt;open&lt;/strong&gt; and we want to close it, or vice versa. So we check if the accordion is open on line # 9.&lt;/li&gt;
&lt;li&gt;We need the &lt;code&gt;maxHeight&lt;/code&gt; property of the content blockʼs &lt;code&gt;style&lt;/code&gt; attribute. We will use it to &lt;strong&gt;control the height&lt;/strong&gt; of the block. So if the accordion is open, then we must make sure that &lt;code&gt;maxHeight&lt;/code&gt; it is set to &lt;code&gt;xxOpenHeight&lt;/code&gt;. See line #10.&lt;/li&gt;
&lt;li&gt;Now we will use &lt;code&gt;setTimeout&lt;/code&gt; recursively to reduce the &lt;code&gt;maxHeight&lt;/code&gt; bit by bit. Weʼll call it every few milliseconds until we have shut the accordion. We create our &lt;code&gt;shutAccordion&lt;/code&gt; function on lines #12-26.&lt;/li&gt;
&lt;li&gt;Hmm … &lt;code&gt;maxHeight&lt;/code&gt; is a string. Urk. So we will &lt;strong&gt;convert it to a number&lt;/strong&gt; with &lt;code&gt;parseInt&lt;/code&gt; to decrement it. Then weʼll recreate the string. We get the integer on line #13. If our &lt;code&gt;maxHeight&lt;/code&gt; is still greater than zero, we subtract 25 from it on lines #16-18. Then we call our &lt;code&gt;shutAccordion&lt;/code&gt; function recursively. We set the timeout to 10 milliseconds. Thatʼs a nice frame rate.&lt;/li&gt;
&lt;li&gt;When the &lt;code&gt;maxHeight&lt;/code&gt; reaches zero, we set &lt;code&gt;accordion.open&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt; (line #25) and weʼre &lt;strong&gt;good to go&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Now that weʼve defined our &lt;code&gt;shutAccordion&lt;/code&gt; recursive function, we need to call it to &lt;strong&gt;start the recursion&lt;/strong&gt; (animation). We do so on line #28.&lt;/li&gt;
&lt;li&gt;But what if the accordion is closed? Then we set the &lt;code&gt;maxHeight&lt;/code&gt; to zero on line #33 to be sure. This also covers the initial case.&lt;/li&gt;
&lt;li&gt;We define our &lt;code&gt;openAccordion&lt;/code&gt; recursive function on lines #35-45.&lt;/li&gt;
&lt;li&gt;Again, we begin by parsing the height from &lt;code&gt;maxHeight&lt;/code&gt; on line #36. And we immediately &lt;strong&gt;set the accordion to “open”&lt;/strong&gt; on line #38. This has no visual effect yet because the height of the content block is currently zero.&lt;/li&gt;
&lt;li&gt;Now, on lines #40-44, if the &lt;code&gt;maxHeight&lt;/code&gt; is still less than the measured &lt;code&gt;openHeight&lt;/code&gt;, we add 15 pixels to the &lt;code&gt;maxHeight&lt;/code&gt;. Then we call the function again using &lt;code&gt;setTimeout&lt;/code&gt; to delay for 10 milliseconds. When the content block is fully open, this conditional will fail and &lt;strong&gt;the recursion will stop&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;And, as with the &lt;code&gt;shutAccordion&lt;/code&gt; function above, we have to call the function to start the animation. We do this on line #47.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Not that difficult!&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it all together
&lt;/h2&gt;

&lt;p&gt;Try the final example above. You might note an issue with the content of the content block &lt;strong&gt;becoming visible instantly&lt;/strong&gt;. This happens even though the height is zero.&lt;/p&gt;

&lt;p&gt;That is because we havenʼt hidden the overflow. We can do that by adding &lt;code&gt;overflow-y: hidden&lt;/code&gt; to the &lt;code&gt;.sb-accordion-content&lt;/code&gt; CSS.&lt;/p&gt;

&lt;p&gt;Once we do that, then our accordion should animate flawlessly. Now disable JavaScript in the DevTools of your browser. Check it out. It still works. It just doesnʼt animate. But we can live with that, right?&lt;/p&gt;

&lt;p&gt;And if you &lt;strong&gt;disable both CSS and JavaScript&lt;/strong&gt;, then &lt;em&gt;it still works fine!&lt;/em&gt; It even works on the &lt;a href="https://lynx.invisible-island.net/"&gt;Lynx text-only browser&lt;/a&gt;, which doesn't recognize the &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; element. But that just means that the accordion is always open.&lt;/p&gt;

&lt;p&gt;Again, we can live with that.&lt;/p&gt;

&lt;p&gt;Thatʼs progressive enhancement for you. Our code is simple and responsive and &lt;strong&gt;works for everyone&lt;/strong&gt;. And if you have CSS and JS enabled, then you get a nicer experience.&lt;/p&gt;

</description>
      <category>craftcode</category>
      <category>javascript</category>
      <category>progressiveenhancement</category>
      <category>responsive</category>
    </item>
    <item>
      <title>Reponsive by default</title>
      <dc:creator>Charles F. Munat</dc:creator>
      <pubDate>Tue, 14 Nov 2023 03:01:01 +0000</pubDate>
      <link>https://forem.com/craft-code/reponsive-by-default-mjb</link>
      <guid>https://forem.com/craft-code/reponsive-by-default-mjb</guid>
      <description>&lt;p&gt;Until we use CSS to wreck it, that is.&lt;/p&gt;

&lt;p&gt;Many developers believe that it is tricky to ensure &lt;strong&gt;responsiveness in web design&lt;/strong&gt;. And that achieving a design that works on any screen resolution or aspect ratio is difficult.&lt;/p&gt;

&lt;p&gt;Too many developers are not willing to make that effort. The consequence? Poorly-coded websites everywhere you look.&lt;/p&gt;

&lt;p&gt;Unresponsive? Whereʼs a defibrillator when you need one?&lt;/p&gt;

&lt;p&gt;Anyone who uses a smart phone to surf the internet encounters such sites daily. Unless like many people you hate the public sphere and spend all your time on Big Tech sites such as F**k or Instagram.&lt;/p&gt;

&lt;p&gt;But here is a secret that many web developers have forgotten, if we ever knew it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HTML is responsive by default.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What that means is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;More often than not, we use CSS to destroy responsiveness rather than to maintain or enhance it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Itʼs not intentional, of course. Few realize that thatʼs what weʼre doing even as we do it. And this is because we start construction with the roof instead of the foundation.&lt;/p&gt;

&lt;p&gt;First we use CSS to destroy responsiveness. Then we add a few media query hacks to try to get back some semblance of it. HTML is often an afterthought. All we need are &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; elements, right? Lots of ʼem.&lt;/p&gt;

&lt;p&gt;It is like designing a car by building the skin first, then trying to force the engine, drivetrain, etc. to fit. You can do it that way, but it wonʼt be comfortable, safe, or maintainable.&lt;/p&gt;

&lt;p&gt;Better to build it from the wheels up for performance and safety, and then to find an attractive body to fit over it. Why not do it right from the outset?&lt;/p&gt;

&lt;p&gt;We can make our sites both responsive and attractive by following a simple approach. Donʼt code outside-in. &lt;strong&gt;Code inside-out.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the foundation
&lt;/h2&gt;

&lt;p&gt;Begin by creating your component, page, or even the whole site in &lt;strong&gt;semantic HTML&lt;/strong&gt;. No CSS at all. No JavaScript.&lt;/p&gt;

&lt;p&gt;Add the &lt;code&gt;&amp;lt;!DOCTYPE&amp;gt;&lt;/code&gt; and the &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; (donʼt forget the &lt;code&gt;lang&lt;/code&gt; attribute), &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; elements. Then add the &lt;code&gt;viewport&lt;/code&gt; and &lt;code&gt;charset&lt;/code&gt; &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; elements and a &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; to the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; element.&lt;/p&gt;

&lt;p&gt;Here, then, is your page foundation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt;
      &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1"&lt;/span&gt; 
      &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Responsive by default&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- content goes here --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now youʼre ready to be &lt;strong&gt;responsive by default&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Build the body next. Start with the landmark elements. At the top level is &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt;. Note how all the virtual DOM libraries screw this up by putting a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; at the top level. They donʼt care about correct HTML. But coding by hand we can get it right.&lt;/p&gt;

&lt;p&gt;Add the other landmark elements as needed: &lt;code&gt;&amp;lt;header&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;footer&amp;gt;&lt;/code&gt; and optionally a &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt; element. Nest these in &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt; if you prefer.&lt;/p&gt;

&lt;p&gt;Hundreds of articles and tutorials have explained how to nest heading elements. Itʼs easy! &lt;strong&gt;Yet more than half of all web sites still get it wrong.&lt;/strong&gt; Why? Craft Coders get this right.&lt;/p&gt;

&lt;p&gt;Moving on, you may want an  element with nested &lt;code&gt;&amp;lt;section&amp;gt;&lt;/code&gt; elements. And, of course, &lt;strong&gt;properly nested&lt;/strong&gt; &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; to &lt;code&gt;&amp;lt;h6&amp;gt;&lt;/code&gt; elements. How about an &lt;code&gt;&amp;lt;aside&amp;gt;&lt;/code&gt;? Is there a &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt;? Be sure that your use of each is correct and &lt;a href="https://validator.w3.org/"&gt;standards compliant&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;How do you know if youʼve done this right? Many &lt;a href="https://www.wmtips.com/tools/html-analyzer/"&gt;HTML tools&lt;/a&gt; will allow you to see your page hierarchy. Here is the &lt;a href="https://www.wmtips.com/tools/html-analyzer/#https://craft-code.dev/"&gt;Craft Code home page&lt;/a&gt;. But possibly the quickest way is to use the &lt;a href="https://chrome.google.com/webstore/detail/html5-outliner/afoibpobokebhgfnknfndkgemglggomo"&gt;HTML5 outliner extension&lt;/a&gt;. Here is what the HTML5 outline of the Craft Code home page looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnk1xq31ud7g16qucufec.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnk1xq31ud7g16qucufec.png" alt="The HTML5 Outliner shows a text outline of the page with properly nested headings." width="768" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see that it is a proper page hierarchy with correctly-nested heading elements.&lt;/p&gt;

&lt;p&gt;The best resource for HTML help that weʼve found is &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element"&gt;Mozilla Developer Network&lt;/a&gt; (MDN). Of course, the final arbiter is the &lt;a href="https://html.spec.whatwg.org/multipage/"&gt;HTML standard itself&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Work your way down through your page hierarchy. Use the correct semantic element for the type of content. For example, an &lt;code&gt;&amp;lt;address&amp;gt;&lt;/code&gt; element for a street address.&lt;/p&gt;

&lt;p&gt;Parse through the text mentally and ask, What is this? Is there a way to make clear to the browser the type of this content with a semantic HTML element?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How many of these do you know?&lt;/strong&gt; How many do you use regularly?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; for paragraphs, &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; for links, &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; for buttons.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;summary&amp;gt;&lt;/code&gt; for text that &lt;strong&gt;might not be of interest&lt;/strong&gt;. We can hide it but keep it available to the user.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;ol&amp;gt;&lt;/code&gt; with &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt; for &lt;strong&gt;ordered lists&lt;/strong&gt;—lists in which &lt;strong&gt;the order of elements matters.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt; with &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt; for &lt;strong&gt;unordered lists&lt;/strong&gt;—lists in which &lt;strong&gt;the order of elements doesnʼt matter.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;dl&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;dt&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;dd&amp;gt;&lt;/code&gt; with &lt;code&gt;&amp;lt;dfn&amp;gt;&lt;/code&gt; (if a definition) for &lt;strong&gt;description lists or glossaries&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;figure&amp;gt;&lt;/code&gt; with &lt;code&gt;&amp;lt;figcaption&amp;gt;&lt;/code&gt; for &lt;strong&gt;figures&lt;/strong&gt;. They are more common than you might think. Great way to caption images, charts, graphs, etc.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;fieldset&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;legend&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;option&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;optgroup&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;datalist&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; for &lt;strong&gt;forms&lt;/strong&gt;. Do you know what all these do? Why not use them properly?&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;caption&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;thead&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;tbody&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;tfoot&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;col&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;colgroup&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;tr&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;th&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;td&amp;gt;&lt;/code&gt; for &lt;strong&gt;tabular data&lt;/strong&gt;. And only for tabular data. You will rarely need a table. Avoid when possible.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; for your &lt;strong&gt;images&lt;/strong&gt;. Or &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; with the &lt;code&gt;srcset&lt;/code&gt; attribute. Donʼt forget the &lt;code&gt;alt&lt;/code&gt; attribute. Consider wrapping these in &lt;code&gt;&amp;lt;figure&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;There are many other worthwhile &lt;strong&gt;flow elements&lt;/strong&gt;, such as:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;code&amp;gt;&lt;/code&gt; for &lt;strong&gt;programming code&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; for &lt;strong&gt;pre-formatted text&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;mark&amp;gt;&lt;/code&gt; for &lt;strong&gt;highlighted text&lt;/strong&gt;. (Great with search results.)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;ins&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;del&amp;gt;&lt;/code&gt; for &lt;strong&gt;insertions&lt;/strong&gt; and &lt;strong&gt;deletions&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;abbr&amp;gt;&lt;/code&gt; for &lt;strong&gt;abbreviations&lt;/strong&gt;. Use the &lt;code&gt;title&lt;/code&gt; attribute for the expansion of the abbreviation.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;em&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;strong&amp;gt;&lt;/code&gt; for &lt;em&gt;emphasis&lt;/em&gt; and &lt;strong&gt;strong emphasis&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;q&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;blockquote&amp;gt;&lt;/code&gt; for &lt;strong&gt;quotations&lt;/strong&gt; (inline and block, respectively).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;cite&amp;gt;&lt;/code&gt; for &lt;strong&gt;citations&lt;/strong&gt;. Have you ever used this one? But you must have cited someone at some time. No?&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;sub&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;sup&amp;gt;&lt;/code&gt; for &lt;strong&gt;subscript&lt;/strong&gt; and &lt;strong&gt;superscript&lt;/strong&gt; letters, respectively (great for footnotes).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;time&amp;gt;&lt;/code&gt; for &lt;strong&gt;dates&lt;/strong&gt; and &lt;strong&gt;times&lt;/strong&gt;. Again, ever used it?&lt;/li&gt;
&lt;li&gt;a few more obscure elements: &lt;code&gt;&amp;lt;kbd&amp;gt;&lt;/code&gt; for &lt;strong&gt;keyboard input&lt;/strong&gt;. &lt;code&gt;&amp;lt;samp&amp;gt;&lt;/code&gt; for &lt;code&gt;&amp;lt;samp&amp;gt;&lt;/code&gt; (from a computer program). &lt;code&gt;&amp;lt;var&amp;gt;&lt;/code&gt; for &lt;strong&gt;mathematical variables&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why is it that so many of us try to represent the semantics of these data with CSS alone? We can encode the semantics right into the HTML with ease and then add the visuals with CSS by tag name.&lt;/p&gt;

&lt;p&gt;But most devs—letʼs be honest—who want to apply a style to a &lt;strong&gt;time&lt;/strong&gt; will add a &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; element and a utility class. The &lt;code&gt;&amp;lt;time&amp;gt;&lt;/code&gt; element? Eh! Never heard of it.&lt;/p&gt;

&lt;p&gt;Or weʼll mark up a &lt;strong&gt;quotation&lt;/strong&gt; by adding quotation marks rather than using the &lt;code&gt;&amp;lt;q&amp;gt;&lt;/code&gt; element. Donʼt we know that the &lt;code&gt;&amp;lt;q&amp;gt;&lt;/code&gt; element can add the quotation marks for us? And that we can use CSS to choose the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/quotes"&gt;quote style&lt;/a&gt;?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;quotes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"“"&lt;/span&gt; &lt;span class="s1"&gt;"”"&lt;/span&gt; &lt;span class="s1"&gt;"‘"&lt;/span&gt; &lt;span class="s1"&gt;"’"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And how many of us have remembered to cite the source?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;q&lt;/span&gt;
  &lt;span class="na"&gt;cite=&lt;/span&gt;&lt;span class="s"&gt;"https://craft-code.dev/essays/code/responsive-by-default"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;abbr&lt;/span&gt; &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"HyperText Markup Language"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;HTML&lt;span class="nt"&gt;&amp;lt;/abbr&amp;gt;&lt;/span&gt;
  is responsive by default.
&lt;span class="nt"&gt;&amp;lt;/q&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But how does an actual page look? Iʼve created a &lt;a href="https://craft-code.dev/essays/code/responsive-by-default/example"&gt;responsive-by-default example&lt;/a&gt; to illustrate.&lt;/p&gt;

&lt;p&gt;Is it ugly as sin? You bet. But is it &lt;strong&gt;usable&lt;/strong&gt;? Iʼd argue yes. More importantly, is it responsive? Well, here it is on my phone. Try it on yours.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6808frlp37tloeqipees.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6808frlp37tloeqipees.png" alt="Our plain HTML page is easy to read and fully usable by smart phone." width="768" height="550"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ugly, true. But usable on any screen or screen reader.&lt;/p&gt;

&lt;p&gt;We can also take a look at the page using the &lt;a href="https://craft-code.dev/essays/code/responsive-by-default"&gt;Lynx text-only browser&lt;/a&gt;. No CSS. No JS. No images. Nothing but text. So does it work? Take a look:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftrn3lub61alm4j6g9qn4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftrn3lub61alm4j6g9qn4.png" alt="The responsive HTML-only page is perfectly usable on the Lynx text-only browser." width="768" height="543"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What about performance?
&lt;/h2&gt;

&lt;p&gt;So. How does this score in &lt;a href="https://chrome.google.com/webstore/detail/lighthouse/blipmdconlkpinefehnmjammfjpmpbjk"&gt;Lighthouse&lt;/a&gt;?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fta9lsu4t3wv9gxk2x5sr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fta9lsu4t3wv9gxk2x5sr.png" alt="Lighthouse says 100% on performance, accessibility, and best practices, but 88% on SEO. Urk." width="768" height="255"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So thatʼs looking pretty g… whoa! What the heck is up with that 88?&lt;/p&gt;

&lt;p&gt;Well, partly it is because we have added the &lt;code&gt;&amp;lt;meta content="noindex" name="robots"&amp;gt;&lt;/code&gt; element to the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;. This prevents search engines from indexing the page. We donʼt want users landing on this example page thinking that it is an actual site page! But if we remove that, we still achieve only a &lt;strong&gt;95%&lt;/strong&gt;. What gives?&lt;/p&gt;

&lt;p&gt;This is actually a problem with the &lt;strong&gt;browser&lt;/strong&gt;. Chromeʼs &lt;strong&gt;default stylesheet&lt;/strong&gt; does not enforce a minimum “tap size” for links and buttons. The links in the unordered lists are too closely spaced (by browser default). Chrome oughta give more room for fat fingers. Like mine.&lt;/p&gt;

&lt;p&gt;But users can zoom, we will fix it with CSS, and it is hard to imagine a phone user without CSS. So weʼll leave it. (We could wrap the links in &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; elements to get that space, but thatʼs a hack, so weʼll avoid it.)&lt;/p&gt;

&lt;p&gt;There we have it. &lt;strong&gt;Responiveness is not something we add to our web pages.&lt;/strong&gt; It is something we &lt;em&gt;destroy&lt;/em&gt;—if weʼre not careful and thoughtful as we code. Keep it responsive.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>a11y</category>
      <category>responsive</category>
      <category>semantichtml</category>
    </item>
    <item>
      <title>Are you a CRAFT Coder?</title>
      <dc:creator>Charles F. Munat</dc:creator>
      <pubDate>Sun, 05 Nov 2023 23:37:18 +0000</pubDate>
      <link>https://forem.com/craft-code/are-you-a-craft-coder-b0b</link>
      <guid>https://forem.com/craft-code/are-you-a-craft-coder-b0b</guid>
      <description>&lt;p&gt;&lt;strong&gt;Craft Code&lt;/strong&gt; is code made with care, skill, and ingenuity. It is as simple as it can be and no simpler. It is state-of-the-art, elegant, and &lt;em&gt;bespoke&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;So are you a Craft Coder?&lt;/p&gt;

&lt;h2&gt;
  
  
  You may be a Craft Coder if ...
&lt;/h2&gt;

&lt;p&gt;You follow these four practices. (There are many more, but these are the essentials.)&lt;/p&gt;

&lt;h3&gt;
  
  
  1. You put quality before quantity
&lt;/h3&gt;

&lt;p&gt;This is not to say that quantity (speed) is unimportant. But the way to achieve speed is by improving your skill, not by rushing.&lt;/p&gt;

&lt;p&gt;Long ago I was a fitter/welder building rail cars. When I started, my foreman told me to build bolsters, a part of the car. I was very slow and sloppy. I could build three per day working as hard as I could.&lt;/p&gt;

&lt;p&gt;The old guy next to me appeared to be standing still. No rush at all. And yet he built ten &lt;em&gt;impressive&lt;/em&gt; bolsters per day &lt;em&gt;every&lt;/em&gt; day. WTF?&lt;/p&gt;

&lt;p&gt;My boss told me not to worry about it. &lt;strong&gt;Focus on quality and speed will come, he said.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A year later I set the company record at fourteen bolsters in a day, and every one was flawless.&lt;/p&gt;

&lt;p&gt;The mistake that new devs make is to focus on &lt;em&gt;speed&lt;/em&gt; and to allow themselves to write sloppy code. Craft Coders know better.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Practice does not make perfect. Practice makes permanent. Repeat the same mistakes over and over, and you donʼt get any closer to Carnegie Hall. ~ Sarah Kay&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb3v0q2ou77daszch45wc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb3v0q2ou77daszch45wc.png" alt="Craft Code FTW" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. You keep things as simple as practicable, but no simpler
&lt;/h3&gt;

&lt;p&gt;Occam's Razor is a famous dictum: &lt;em&gt;do not needlessly multiply entities&lt;/em&gt;. This is often paraphrased as "the simplest solution is the best".&lt;/p&gt;

&lt;p&gt;But that's wrong.&lt;/p&gt;

&lt;p&gt;If that were the case, then a simple bad solution would be better than a more complex good solution. Huh?&lt;/p&gt;

&lt;p&gt;The key word is "needlessly". What William of Ockham was saying was, "the simplest solution &lt;em&gt;all else being equal&lt;/em&gt; is the best one". That's a big difference.&lt;/p&gt;

&lt;p&gt;If two solutions are &lt;em&gt;equal&lt;/em&gt; solutions, then choose the simpler one. Don't add anything you don't need. &lt;strong&gt;No gratuitous nuthin'.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's how a Craft Coder codes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Complexity is a sign of technical immaturity. ~ Daniel T. Ling&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  3. You prefer bespoke code whenever practicable.
&lt;/h3&gt;

&lt;p&gt;There are times when commodity code makes more sense. But commodity code is always inefficient. When you use code written by others, they didn't write it for &lt;em&gt;you&lt;/em&gt;. They wrote it for a wide variety of coders and situations.&lt;/p&gt;

&lt;p&gt;Hence, code &lt;em&gt;that you don't need&lt;/em&gt; is endemic to commodity code. There is no escaping it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Writing specific, bespoke code creates the most efficient, performant code.&lt;/strong&gt; No needless entities! Assuming, of course, that you are a good enough coder (see #1 above).&lt;/p&gt;

&lt;p&gt;Craft Coders are confident, consistent bespoke coders. They can write commodity code, but rather than a first resort, they see it as a last one.&lt;/p&gt;

&lt;p&gt;This is not to disparage re-use. Not at all. But first write the code yourself, &lt;em&gt;then&lt;/em&gt; re-use it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Avoid hasty abstractions (AHA). ~ Kent C. Dodds&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  4. You embrace a comprehensive approach
&lt;/h3&gt;

&lt;p&gt;It is very easy for web developers to suffer from &lt;strong&gt;tunnel vision&lt;/strong&gt;. When all you have is a hammer, everything starts to look like a nail, right?&lt;/p&gt;

&lt;p&gt;If you are working in a large enterprise as another cog in the wheel, then that might be for the best. It is assembly-line, commodity code, after all. Keep those blinders on.&lt;/p&gt;

&lt;p&gt;But Craft Coders engage themselves in the entire process of web development.  And that is &lt;em&gt;so much more&lt;/em&gt; than cranking out code.&lt;/p&gt;

&lt;p&gt;First, there is accessibility, as specified in the &lt;a href="https://www.w3.org/WAI/WCAG22/quickref/"&gt;WCAG 2.2 guidelines&lt;/a&gt; and related advice. To a Craft Coder, accessibility is not an afterthought, but something foundational. Nothing gets built without putting accessibility first.&lt;/p&gt;

&lt;p&gt;And UX, of course, although true accessibility incorporates UX. If an application is not usable, then how can it be accessible?&lt;/p&gt;

&lt;p&gt;But as many coders are now starting to realize, the most important approach of all is to &lt;a href="https://www.sustainablewebmanifesto.com/"&gt;make it sustainable&lt;/a&gt;. Craft Coders do so without fail. After all, there  is little point in remodeling the kitchen if you're busy burning down the house.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Craft Coders consider &lt;em&gt;all&lt;/em&gt; aspects of web development in their work.&lt;/strong&gt; Do you?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There is no Planet B. ~ Mike Berners-Lee (son of Tim)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  So, are you a Craft Coder?
&lt;/h2&gt;

&lt;p&gt;If so, speak up. Craft Coding is a noble calling. But if new coders don't hear about it, then our numbers will dwindle. And that will be a sad day for web development.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Weigh in in the comments.&lt;/strong&gt; What do you think? Are you a Craft Coder or a commodity coder? And why? And no judgement! The world needs commodity coders, too.&lt;/p&gt;

</description>
      <category>craftcode</category>
      <category>webdev</category>
      <category>sustainability</category>
      <category>a11y</category>
    </item>
  </channel>
</rss>
