<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Synaptic Spillage]]></title><description><![CDATA[Hi, I’m Todd. I've been an entrepreneur, developer, website builder, network cabler, fast-car driver and spell-check ignorer for over 20 years. I dig Python, Ja]]></description><link>https://toddesposito.com</link><generator>RSS for Node</generator><lastBuildDate>Tue, 21 Apr 2026 10:30:24 GMT</lastBuildDate><atom:link href="https://toddesposito.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Building a CLI in Node for DevPail]]></title><description><![CDATA[This is the second in a series of articles about building DevPail, my general-purpose development environment tooling tool. You don't have to read the previous article to learn something here, but the context may help you understand what I'm talking ...]]></description><link>https://toddesposito.com/building-a-cli-in-node-for-devpail</link><guid isPermaLink="true">https://toddesposito.com/building-a-cli-in-node-for-devpail</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Gulp]]></category><dc:creator><![CDATA[Todd Esposito]]></dc:creator><pubDate>Sun, 30 Jan 2022 19:38:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1643559602241/wU1ifQza_.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>This is the second in a <a target="_blank" href="https://toddesposito.com/series/simplify-your-tooling">series of articles</a> about building <strong>DevPail</strong>, my general-purpose development environment tooling tool. You don't have to read the previous article to learn something here, but the context may help you understand what I'm talking about.</p>
</blockquote>
<h2 id="heading-the-story-so-far">The story so far...</h2>
<p>In the <a target="_blank" href="https://toddesposito.com/devpail-1-introduction">first article</a>, I introduced my solution to tooling churn, which is to keep all the tooling for each of my projects into an isolated, transportable environment I'm calling a <strong>DevPail</strong>. It's a <a target="_blank" href="https://docker.com">Docker</a> <em>container</em> where the tooling runs, coupled with a <em>volume</em> which holds all the tooling and development artifacts. However, the commands needed to use <strong>DevPail</strong> are fairly involved, so let's solve that issue today by creating a Command Line Interface (CLI) to handle all these tedious details.</p>
<h2 id="heading-adding-a-cli-to-the-project">Adding a CLI to the project</h2>
<p><strong>DevPail</strong>'s project directory so far is pretty bare:</p>
<pre><code class="lang-text">devpail/
  - imagesrc/
    - homedir/
    - Dockerfile
</code></pre>
<h3 id="heading-node-configuration">Node configuration</h3>
<p>Since we've chosen to build or CLI in Node, we need to initialize a Node package:</p>
<pre><code class="lang-bash">$ <span class="hljs-built_in">cd</span> devpail
$ mkdir src
$ npm init -y
</code></pre>
<p>Here we're creating the <code>src</code> directory to hold our CLI source code, and asked <code>npm</code> to create a <code>package.json</code>.</p>
<h3 id="heading-coding-from-30000-feet">Coding from 30,000 feet</h3>
<p>My habit is to build "kinda" code before actual code. It's real code, but at about 30,000 feet, with zero hope of running. This gives me the basic structure of the code, and a framework I can fill in with the gritty-kitty details as I go.</p>
<p>For <strong>DevPail</strong>, I'll need to be able to:</p>
<ul>
<li>Build the <strong>DevPail</strong> <em>image</em>; let's call this <code>devpail --build</code></li>
<li>Run a shell in the <strong>DevPail</strong> <em>container</em> for any project; let's call this <code>devpail --shell</code></li>
<li>Run the <strong>DevPail</strong> tooling inside the <em>container</em> for any project; let's call this <code>devpail</code></li>
</ul>
<h2 id="heading-lets-start-coding">Let's start coding</h2>
<p>Let's create a file called <code>src/cli.js</code> to hold this code, and start building that framework. We know that the first thing we have to do is process those command line options, if any.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> status = processOptions()
<span class="hljs-keyword">if</span> (status) {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`\nDevPail: <span class="hljs-subst">${status}</span>\n`</span>)
}
</code></pre>
<p>As promised, this code totally won't work yet. Let's move down one level:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">processOptions</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">var</span> args = process.argv.slice(<span class="hljs-number">2</span>)
    <span class="hljs-keyword">if</span> (args &amp;&amp; args[<span class="hljs-number">0</span>] === <span class="hljs-string">'--build'</span>) {
        <span class="hljs-keyword">return</span> buildImage()
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (args &amp;&amp; args[<span class="hljs-number">0</span>] === <span class="hljs-string">'--shell'</span>) {
        <span class="hljs-keyword">return</span> runShell()
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">return</span> runContainer()
    }
}
</code></pre>
<p><code>process.argv</code> contains the entire command line, including the command used to start <strong>DevPail</strong>, so we slice it out, and check what's left for command line arguments. We always check that there are any arguments at all, and then if the first one is something we want to act on.</p>
<blockquote>
<p>In our very simple case, the above works well enough. However, for anything even slightly more complex, you'll want to look into a package such as <code>yargs</code> to handle your command line arguments.</p>
</blockquote>
<p>Now that we have our basic flow in place, we can flesh out the details.</p>
<h2 id="heading-fleshing-out-the-details">Fleshing out the details</h2>
<h3 id="heading-building-build">Building <code>--build</code></h3>
<p>We've already know how to build the <strong>DevPail</strong> image from the command line:</p>
<pre><code class="lang-console">docker build -t devpail imagesrc
</code></pre>
<p>Let's adapt that to Node. To run a <code>docker</code>, a shell command, we'll use the <code>child_process</code> module. And to tell <code>docker</code> where the <code>Dockerfile</code> is, we'll need to use the <code>path</code> module. Let's start by including those at the top of our script:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> { spawnSync } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'child_process'</span>)
<span class="hljs-keyword">const</span> path = <span class="hljs-built_in">require</span>(<span class="hljs-string">'path'</span>)
</code></pre>
<p>And then we'll Node-ify that command line:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">buildImage</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'DevPail: Building image "devpail:default"...'</span>)
    spawnSync(
        <span class="hljs-string">'docker'</span>,
        [
            <span class="hljs-string">'build'</span>,
            <span class="hljs-string">'-t'</span>,
            <span class="hljs-string">'devpail:default'</span>,
            path.resolve(__dirname, <span class="hljs-string">'..'</span>, <span class="hljs-string">'imagesrc'</span>)
        ],
        {
            <span class="hljs-attr">shell</span>: <span class="hljs-literal">true</span>,
            <span class="hljs-attr">stdio</span>: <span class="hljs-string">'inherit'</span>
        }
    )
</code></pre>
<p>That <code>path.resolve(...)</code> call uses the inbuilt <code>__dirname</code> constant to get the location of the running script (<code>src/cli.js</code>), and then step up one level, and give us the <code>imagesrc</code> directory in which our <code>Dockerfile</code> is housed.</p>
<p><code>--build</code>: ✔</p>
<h3 id="heading-running-devpail-with-no-arguments">Running <strong>DevPail</strong> with no arguments</h3>
<p>I'm going to skip ahead to the third point in the list, for reasons which will become apparent.</p>
<p>You'll recall that rather lengthy command line which runs the container:</p>
<pre><code class="lang-console">docker run -it --rm -v myproject-tooling:/home/pn/app -v ~/myproject:/home/pn/app/src devpail
</code></pre>
<h4 id="heading-first-pass">First Pass</h4>
<p>Let's make this easier and a bit smarter. </p>
<p>We'll require that you run the <code>devpail</code> command (the one we're building now) from the root of the project you want to "DevPail-ize", as it were. This means that the current working directory should be the target project's name.</p>
<p>The solution will look much like that for <code>--build</code>, but with a few tweaks. Our first pass will look like:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">runContainer</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">var</span> project = path.basename(process.cwd())
    spawnSync(
        <span class="hljs-string">'docker'</span>,
        [
            <span class="hljs-string">'run'</span>,
            <span class="hljs-string">'-it'</span>,
            <span class="hljs-string">'--rm'</span>,
            <span class="hljs-string">'-v'</span>, <span class="hljs-string">`<span class="hljs-subst">${project}</span>-DevPail-Tooling:/home/pn/app`</span>,
            <span class="hljs-string">'-v'</span>, <span class="hljs-string">`<span class="hljs-subst">${process.cwd()}</span>:/home/pn/app/src`</span>,
            <span class="hljs-string">`devpail:default`</span>
        ],
        {
            <span class="hljs-attr">shell</span>: <span class="hljs-literal">true</span>,
            <span class="hljs-attr">stdio</span>: <span class="hljs-string">'inherit'</span>
        }
}
</code></pre>
<h4 id="heading-handling-duplicate-project-folder-names">Handling duplicate project folder names</h4>
<p>Of course, you might use the same name for more than one project, but in different sub-trees, so we'll handle that case by adding a hash of the FULL path to the project's directory:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createProjectName</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">return</span> path.basename(process.cwd()) 
        + <span class="hljs-string">'-'</span> 
        + <span class="hljs-built_in">require</span>(<span class="hljs-string">'crypto'</span>)
            .createHash(<span class="hljs-string">'md5'</span>)
            .update(process.cwd())
            .digest(<span class="hljs-string">'hex'</span>)
            .slice(<span class="hljs-number">0</span>, <span class="hljs-number">8</span>)
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">runContainer</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">var</span> project = createProjectName()
...
</code></pre>
<h4 id="heading-naming-the-container">Naming the container</h4>
<p>Docker will create a name for each running <em>container</em> from a couple random words which bear no relation to the purpose of the <em>container</em>. Let's make that name meaningful. And while we're at it, let's do the same for the <em>container</em>'s hostname, which will be useful later on.</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">runContainer</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">var</span> project = createProjectName()
    spawnSync(
        <span class="hljs-string">'docker'</span>,
        [
...
            <span class="hljs-string">'--name'</span>, <span class="hljs-string">`<span class="hljs-subst">${project}</span>-DevPail`</span>,
            <span class="hljs-string">'--hostname'</span>, project,
            <span class="hljs-string">`devpail:default`</span>
        ],
...
}
</code></pre>
<p><code>devpail</code> (no arguments): ✔</p>
<h3 id="heading-implementing-shell">Implementing <code>--shell</code></h3>
<p>We can run a shell in our <em>container</em> by way of the <code>--entrypoint</code> argument to the <code>docker</code> command, as in:</p>
<p><code>docker -it --rm --entrypoint /bin/bash devpail</code></p>
<p>Of course, the above command line will also need all the other parameters we provided in the section above, and that sounds like <code>--shell</code> is just a special case of the no-arguments version of the <code>devpail</code> command.</p>
<p>Let's revisit <code>processOptions()</code> first:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">processOptions</span>(<span class="hljs-params"></span>) </span>{
...
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (args &amp;&amp; args[<span class="hljs-number">0</span>] === <span class="hljs-string">'--shell'</span>) {
        <span class="hljs-keyword">return</span> runContainer([<span class="hljs-string">'--entrypoint'</span>, <span class="hljs-string">'/bin/bash'</span>])
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">return</span> runContainer()
    }
}
</code></pre>
<p>Now let's handle those arguments in <code>runContainer()</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">runContainer</span>(<span class="hljs-params">opts = []</span>) </span>{
    <span class="hljs-keyword">var</span> project = createProjectName()
    spawnSync(
        <span class="hljs-string">'docker'</span>,
        [
...
            ...opts,
            <span class="hljs-string">`devpail:default`</span>
        ],
...
}
</code></pre>
<p><code>--shell</code>: ✔</p>
<h2 id="heading-next-up-making-gulp-work">Next Up: making Gulp work</h2>
<p>At this point, we can easily build and run <strong>DevPail</strong> for a project, but it doesn't really accomplish anything just yet.</p>
<p>In the next installment of this series, we'll build out a pluggable GulpJS system that will actually do something useful.</p>
<p>I've made huge updates to the publicly available <strong>DevPail</strong> project, on both <a target="_blank" href="https://github.com/tdesposito/DevPail">GitHub</a> and <a target="_blank" href="https://npmjs.org/package/devpail">NPM</a>. It's evolved considerably since the first article, and I'm using it in production for several projects. So please feel to take a look at it. And then tune back in here to read more about the process of creating this tool!</p>
]]></content:encoded></item><item><title><![CDATA[Building a Docker/Gulp-based development environment: Introducing DevPail]]></title><description><![CDATA[This is the first in a series of articles about building DevPail, my general-purpose development environment tooling tool.

Days gone by
Oh, how I long for the days of yore. Especially Tuesdays. Tuesdays of yore were
the best. Back then, building sof...]]></description><link>https://toddesposito.com/devpail-1-introduction</link><guid isPermaLink="true">https://toddesposito.com/devpail-1-introduction</guid><category><![CDATA[Devops]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Gulp]]></category><dc:creator><![CDATA[Todd Esposito]]></dc:creator><pubDate>Sun, 19 Dec 2021 23:30:34 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1639840803799/Bdco4-E_O.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>This is the first in a <a target="_blank" href="https://toddesposito.com/series/simplify-your-tooling">series of articles</a> about building <strong>DevPail</strong>, my general-purpose development environment tooling tool.</p>
</blockquote>
<h2 id="heading-days-gone-by">Days gone by</h2>
<p>Oh, how I long for the days of yore. Especially Tuesdays. Tuesdays of yore were
the <strong>best</strong>. Back then, building software was super simple. You could just fire
up an editor, write some code, and build you software like this:</p>
<pre><code class="lang-console">$ vimacs main.c
$ gcc main.c
$ ./a.out
</code></pre>
<p>Every. Single. Time.</p>
<p>But then you wake up (or move on to 200-level classes), and the world is much
more complex. Now, because your project has grown to multiple source files, in
multiple languages, using multiple technologies, you need... <em>(insert ominous
music)</em> <strong>Tooling.</strong></p>
<h2 id="heading-what-is-this-tooling-of-which-you-speak">What is this "tooling" of which you speak?</h2>
<p>The short version (and that's all you're getting, folks; Google's got you for the long version) is: the software you use to write and manage your software. In the example above, this is the editor with which you write the code, and the compiler/linker package you use to translate the code into an executable. But we're <strong>way</strong> beyond that in today's world. Now, we also have:</p>
<ul>
<li>linters to validate our code is syntactically correct and properly formed</li>
<li>testing frameworks to make sure our code does what we expect it to</li>
<li>development servers to preview our code's operation while we work</li>
<li>packagers to wrap up our code into neat little bundles we can send to others</li>
<li>task runners to manage all of that stuff</li>
<li>so much more, just waiting to guide you down another rabbit hole</li>
</ul>
<p>And not just one of each of those, but one or more for <strong>each and every language we use.</strong> Oh, you say, you're only using one language. I doubt it. For example, for web work you're likely using at least three of HTML, CSS, JavaScript, Python, Ruby and Go and, well, you get the idea.</p>
<blockquote>
<p>Please don't get pedantic on the definition of a language here; Let's agree that these are <strong>at least</strong> dialects whose features and rules we have to learn.</p>
</blockquote>
<h2 id="heading-whats-the-problem-with-tooling">What's the problem with tooling?</h2>
<p>The issue, of course, is that each of these <em>tools</em> is itself <em>software</em>, and as such, undergoes the same churn as all software projects. We find and fix bugs. We add, revise and remove features. We replace some tools entirely in favor of something newer or more well-suited to what we're doing. And some fall into disrepair out of neglect or abandonment.</p>
<p>In a project started just a year ago, the tooling may have become unusable because it's not compatible with the latest version of some other tool, or the underlying platform. In projects even older, this is nearly guaranteed. </p>
<p>I've had the pleasure of having to tell a client that, yes, I can add that relatively small feature to their website, but the cost of revising the tooling will cost more than the feature. And, no, their previous developer didn't keep the tooling specs pinned to the versions which worked back then. This is NOT a happy conversation, for either of us.</p>
<p>In addition, every new platform, library and framework brings with it more tooling. So while the tooling from your last project may work, it'll need at a minimum some tweaks to work with the new stuff.  And when you go back to a previous project and the tool flow you're used to isn't there, you either end up fighting you own tools or "investing" time retrofitting the new tools into the old workflow.</p>
<h2 id="heading-we-need-a-bucket-of-tools-we-can-carry-with-us">We need a bucket of tools we can carry with us</h2>
<p>You know those 5-gallon <em>bucket</em> and <em>apron</em> combos people use to carry their tools from job to job? (Hint: check out the cover image for this article.) Everything's in there: tools, spare parts, old candy wrappers, <strong>everything!</strong> And the entire bucket, unaltered, moves from job to job with ease.</p>
<p>I want one of those for software tooling. So I built one, and I call it <a target="_blank" href="https://github.com/tdesposito/DevPail"><strong>DevPail</strong> (yes, it's on GitHub)</a>. It's not a silver bullet for every possible werewolf, but it makes me happy.</p>
<blockquote>
<p>Please note, the version available on GitHub, as of this writing, is very young, not feature-complete, and not well documented. I'm actively working on it. Patience is a virtue.</p>
</blockquote>
<p>Let's walk through building it together!</p>
<h2 id="heading-designing-devpail">Designing DevPail</h2>
<p>The general philosophy is that we have three concerns in building our development environments, which we want to keep separate:</p>
<ol>
<li><p>All the infrastructure (i.e. NodeJS or Python) lives in the <em>bucket</em>, which can be moved easily from job to job -- and this is important -- without alteration. One ring to rule them all, so to speak.</p>
</li>
<li><p>The project-specific tooling (i.e. <code>node_modules</code> or <code>site-packages</code>) are carried around with the bucket, like the <em>apron</em>. Separate, but connected.</p>
</li>
<li><p>The project's code lives on the host system (well, probably in a source control repo somewhere, but that's picking nits).</p>
</li>
</ol>
<h3 id="heading-the-bucket-docker">The bucket: Docker</h3>
<p>I tried using VMs to contain all my tooling for a project, but for my needs at least, that's like swatting a fly with a sledgehammer. I need a much lighter tool, which doesn't take two minutes to boot. <a target="_blank" href="https://docker.com">Docker</a> provides just what we need.</p>
<h3 id="heading-the-apron-gulp">The apron: Gulp</h3>
<p>I've been using <a target="_blank" href="https://gulpjs.com">Gulp</a> for years, but I'm really tired of needing to copy/paste/tweak/cus/tweak the <code>gulpfile</code> every time I start a new project. While I like Gulp in general, I want a plug-able solution which can be configuration-driven rather than strictly code-driven. Gulp, in its infinite coolness, can be made to meet that objective.</p>
<h3 id="heading-the-tools-everything-else-on-demand-as-needed">The tools: Everything else, on demand, as needed</h3>
<p>Each project, naturally, has it's own requirements for platform and tooling, so those should be configurable easily. Ideally by setting a couple of entries in a <code>package.json</code> file.</p>
<h2 id="heading-enough-exposition-lets-build">Enough exposition, let's build</h2>
<p>Let's start by creating a directory for our project directory, and some subidirectories. Next, let's build a <code>Dockerfile</code>:</p>
<pre><code class="lang-console">$ mkdir -p devpail/imagesrc/homedir
$ cd devpail
$ vimacs imagesrc/Dockerfile
</code></pre>
<p>We'll talk about that <code>homedir</code> entry below, and in the following installments of this series.</p>
<h3 id="heading-choose-a-base-image">Choose a base image</h3>
<p><a target="_blank" href="https://hub.docker.com">DockerHub</a> contains (no pun) images to meet needs both sublime and obscene... ok, wait, that's over the top. There are <strong>lots</strong> of images there, and you'll surely find one you like.</p>
<p>I've chosen one which gives us Python 3.8 and NodeJS 14 because that matches my most common deployment environments. This image includes a non-root user called <code>pn</code> whose home directory is, of course, <code>/home/pn</code>.</p>
<pre><code class="lang-dockerfile"># syntax=docker/dockerfile:1
FROM nikolaik/python-nodejs:python3.8-nodejs14
</code></pre>
<blockquote>
<p>The Dockerfile we're building here is a somewhat simplified version of what you'll see in the GitHub repo, for the sake of clarity.</p>
</blockquote>
<h3 id="heading-add-gulp-to-the-image">Add Gulp to the image</h3>
<p>We'll need the Gulp CLI, so we add that to the image. Not much to see here.</p>
<pre><code class="lang-dockerfile">RUN npm install --global gulp-cli
</code></pre>
<h3 id="heading-setup-our-container-environment">Setup our container environment</h3>
<p>We want to use <code>bash</code> rather than the Docker default <code>sh</code> for reasons which will become apparent later on.</p>
<p>We also want to be able to run any tools we've added via <code>pip</code>/<code>poetry</code> and <code>npm</code>/<code>yarn</code>, so we set up some environment variables which we'll leverage... later on, and update our PATH.</p>
<pre><code class="lang-dockerfile">SHELL ["/bin/bash", "-c"]

ENV PYTHONROOT="/home/pn/app/site-packages"
ENV PYTHONPATH="${PYTHONROOT}/lib/python3.8/site-packages"
ENV NODE_PATH="/home/pn/app/node_modules/.bin"
ENV PATH="/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PYTHONROOT}/bin:${NODE_PATH}"
</code></pre>
<h3 id="heading-create-our-container-directory-structure">Create our container directory structure</h3>
<p>As stated above, our base image gives us a <code>pn</code> user, so we'll make our tools run in the container as that user. It's a best practice to avoid running anything as <code>root</code>, even inside a container.</p>
<p>We'll copy a few files we need into the image from the <code>homedir</code> we made before into the home directory of the <code>pn</code> user, and ensure those files are owned by <code>pn</code>.</p>
<p>We will mount a volume to hold all the per-project tooling under <code>/home/pn/app</code>, and ensure that directory is writable by <code>pn</code>.</p>
<pre><code class="lang-dockerfile">USER pn
COPY --chown=1000:1000 homedir/* /home/pn
WORKDIR /home/pn/app
RUN chown 1000:1000 /home/pn/app
</code></pre>
<h3 id="heading-make-our-containers-server-accessible">Make our container's server accessible</h3>
<p>Our Gulp process will spin up one or more server processes, on ports 3000 through 3009. Yes, it's more than we likely need, but the cost is near zero, and gives us lots of flexibility, so why not? Let's ensure we can connect to those ports from our host.</p>
<pre><code class="lang-dockerfile">EXPOSE 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009
</code></pre>
<h3 id="heading-run-our-development-tooling">Run our development tooling</h3>
<p>Finally, when we run the container, we (usually) want to run our default Gulp task.</p>
<pre><code class="lang-dockerfile">ENTRYPOINT [ "/bin/bash", "-c", "gulp" ]
</code></pre>
<h2 id="heading-build-and-run-the-image">Build and run the image</h2>
<p>Building the image is pretty straightforward. We ask Docker to build the image described by the <code>Dockerfile</code> in <code>imagesrc</code>, and we tag (<code>-t</code>) that image as <code>devpail</code>.</p>
<pre><code class="lang-console">$ docker build -t devpail imagesrc
... (lots of docker output) ...
</code></pre>
<p>Running it will take a bit more work, so we'll walk through the command line bit by bit. Here it is:</p>
<pre><code class="lang-console">$ docker run -it --rm -v myproject-tooling:/home/pn/app -v ~/myproject:/home/pn/app/src devpail
</code></pre>
<ul>
<li><code>docker run</code> tells docker to... um... run an image.</li>
<li><code>-i</code> (<em>interactive</em>) and <code>-t</code> (<em>tty</em>), which we've abbreviated as <code>-it</code>, allows us to interact with the container. Basically, we're "shelling" into the container.</li>
<li><code>--rm</code> (note the double-dash here) tells Docker to remove the container when it exits.</li>
<li><code>-v myproject-tooling:/home/pn/app</code> mounts a <a target="_blank" href="https://docs.docker.com/glossary/#volume">named volume</a> to <code>~/app</code>. This will hold our per-project tooling, keeping it nicely wrapped up.</li>
<li><code>-v ~/myproject:/home/pn/app/src</code> mounts our project's directory. <strong>Replace this with your actual project directory.</strong></li>
<li><code>devpail</code> is the image we want to run.</li>
</ul>
<pre><code class="lang-console">$ docker run ...(as above)
[01:02:34] Local gulp not found in ~
[01:02:34] Try running: npm install gulp
</code></pre>
<p>We've build an image, and then run that image in a container. It didn't <strong>DO</strong> anything, but it's a good start.</p>
<p>In upcoming articles, we'll build our <em>Gulp</em> tasks, simplify running all those <code>docker</code> commands, and make <strong>DevPail</strong> a bit smarter. Not necessarily in that order. Stay tuned!</p>
]]></content:encoded></item><item><title><![CDATA[This Just In: Yet Another Local Web Server]]></title><description><![CDATA[Fact is, I should not have built this code. I'm sure that somewhere, there's a suitable NPM or PyPi package (or something in some other language) which would have worked out just fine. But nothing I saw on the first page of my search results seemed l...]]></description><link>https://toddesposito.com/this-just-in-yet-another-local-web-server</link><guid isPermaLink="true">https://toddesposito.com/this-just-in-yet-another-local-web-server</guid><category><![CDATA[Node.js]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[webdev]]></category><dc:creator><![CDATA[Todd Esposito]]></dc:creator><pubDate>Sat, 02 Oct 2021 05:17:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1633151863634/C912BZbe4.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Fact is, I should not have built this code. I'm sure that somewhere, there's a suitable NPM or PyPi package (or something in some other language) which would have worked out just fine. But nothing I saw on the first page of my search results seemed like it was both the correct functionality AND light-weight enough.</p>
<p>I'm using GitPages to host the <a target="_blank" href="https://tdesposito.github.io/EH-WebComponents">documentation and demos</a> for my <a target="_blank" href="https://github.com/tdesposito/EH-WebComponents">little Web Components library</a>. The docs are Markdown files, which GitPages dynamically converts to HTML for me. So surely there's a local equivalent I can use to test my updates prior to publication, right?</p>
<h2 id="jekyll-et-al-not-really-to-the-rescue">Jekyll, et al, Not Really to the Rescue</h2>
<p>The <a target="_blank" href="https://docs.github.com/en/pages/setting-up-a-github-pages-site-with-jekyll/testing-your-github-pages-site-locally-with-jekyll">"official" method</a> is to install Jekyll and a bunch of other tooling. Jekyll is written in Ruby, and I don't use Ruby myself (no offense), so it seemed like a bridge too far to install an entire language system just to test this one thing. I would spend more time configuring that then writing the docs. Bleh.</p>
<p>There are several "markdown web servers" out there too, but they uniformly seem to just want to server markdown, and not do that nifty GitPages translate-on-the-fly thing. Maybe I just missed the silver bullet. Too late if I did, because I wrote one in about 90 minutes.</p>
<h2 id="i-love-to-tinker">I LOVE to tinker</h2>
<p>I know JavaScript reasonably well, so why not just dive in? I legitimately spent less time building what I present here than I had researching a pre-built solution.</p>
<p>So let's talk code, eh?</p>
<p>First, as in any project a complete, well-thought-out plan was needed. Except I didn't make one, exactly. As is my habit, I start by writing code that just won't work, but expresses the basics:</p>
<ul>
<li>I need an HTTP server</li>
<li>which sends files from the current directory</li>
<li>and processes Markdown into HTML on the fly</li>
<li>and if a request for an <code>.html</code> resource comes in but no such file exists, try the corresponding <code>.md</code> file</li>
<li>and if the URL looks like a directory, serve up <code>index.html</code> (which could actually be <code>index.md</code>)</li>
<li>and gives meaningful errors as needed</li>
</ul>
<p>We'll set up a project</p>
<pre><code class="lang-Console">mkdir mdserver
cd mdserver
npm init -y
</code></pre>
<p>and add this code as <code>index.js</code>:</p>
<pre><code class="lang-JavaScript"><span class="hljs-keyword">const</span> http = <span class="hljs-built_in">require</span>(<span class="hljs-string">'http'</span>)

<span class="hljs-comment">// create a simple HTTP server</span>
<span class="hljs-keyword">const</span> server = http.createServer(<span class="hljs-function">(<span class="hljs-params">req, rsp</span>) =&gt;</span> {
  <span class="hljs-comment">// convert the requested URL to a local pathname</span>
  <span class="hljs-keyword">let</span> pathname = resolveUrl(req.url)

  <span class="hljs-comment">// send the content from that pathname, or an error</span>
  <span class="hljs-keyword">return</span> resolvePath(pathname)
})

<span class="hljs-comment">// listen for requests.</span>
server.listen(<span class="hljs-number">3000</span>, <span class="hljs-string">"localhost"</span>, <span class="hljs-function">() =&gt;</span> {
   <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`ehMDserver: running at http://localhost:3000/`</span>)
})
</code></pre>
<p>This stub doesn't won't actually work, because I haven't defined <code>resolveURL()</code> and <code>resolvePath()</code>. That's OK, it's all part of my process of starting from a relatively high level, and then drilling down into the details.</p>
<h2 id="lets-convert-the-url-to-a-local-pathname">Let's convert the URL to a local pathname</h2>
<p>This one is currently super simple, but later we'll add some additional features, so hang tight:</p>
<pre><code class="lang-JavaScript"><span class="hljs-keyword">const</span> pathlib = <span class="hljs-built_in">require</span>(<span class="hljs-string">'path'</span>)
...
function resolveUrl(url) {
  <span class="hljs-keyword">return</span> pathlib.resolve(<span class="hljs-string">`./<span class="hljs-subst">${url}</span>`</span>)
}
</code></pre>
<p>Now you may be thinking, <em>"how does he know we'll add features later?"</em> Well, truthfully, I didn't. But the principle of mapping out the high-level steps means that if this never evolves, the main function is still readable. And if it does evolve, the main function is still readable.</p>
<h2 id="now-lets-send-a-file">Now let's send a file</h2>
<p>For starters, let's just send, verbatim, whatever file is requested.</p>
<pre><code class="lang-JavaScript"><span class="hljs-keyword">const</span> fs= <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>)
...
function resolvePath(path) {
  <span class="hljs-keyword">if</span> (fs.existsSync(path)) {
    <span class="hljs-keyword">if</span> (fs.lstatSync(path).isFile())  {
      <span class="hljs-keyword">return</span> sendFile(path)
    }
  }
}
</code></pre>
<p>You'll note that I've once again called a non-existent function, but -- again -- you get what this thing is doing.</p>
<p>Now we'll need to implement <code>sendFile()</code>:</p>
<pre><code class="lang-JavaScript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sendFile</span>(<span class="hljs-params">path</span>) </span>{
  body = fs.readFileSync(path)
  rsp.writeHead(<span class="hljs-number">200</span>,
    {
      <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'text/plain'</span>,
      <span class="hljs-string">'Content-Length'</span>: Buffer.byteLength(body)
    }
  )
  rsp.end(body)
}
</code></pre>
<p>We read the file from disk, send HTTP headers <code>Content-Type</code> and <code>Content-Length</code> and send the file contents to the client.</p>
<p>And now we have a problem: our <code>rsp</code> (response) object, which came into our main function, needs to be here too, so we'll pass it all down the chain. In fact, as long as we're at it, let's send the <code>req</code> (request object) as well. Could prove useful later, and for now we're not worried about a linter complaining.</p>
<pre><code class="lang-JavaScript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">resolvePath</span>(<span class="hljs-params">req, rsp, path</span>) </span>{
...
      return sendFile(req, rsp, path)
...
}
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sendFile</span>(<span class="hljs-params">req, rsp, path</span>) </span>{
  body = fs.readFileSync(path)
  rsp.writeHead(<span class="hljs-number">200</span>,
    {
      <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'text/plain'</span>,
      <span class="hljs-string">'Content-Length'</span>: Buffer.byteLength(body)
    }
  )
  rsp.end(body)
}

<span class="hljs-keyword">const</span> server =  http.createServer(<span class="hljs-function">(<span class="hljs-params">req, rsp</span>) =&gt;</span> {
...
  return resolvePath(req, rsp, pathname)
})
</code></pre>
<p>Now when we run <code>node index.js</code> and visit <code>http://localhost:3000/index.js</code> we'll get back the content of <code>index.js</code> unaltered.</p>
<p>But we'll need some special handling for Markdown files, so let's add that special case to our <code>sendFile()</code> function:</p>
<pre><code class="lang-JavaScript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sendFile</span>(<span class="hljs-params">req, rsp, path</span>) </span>{
  <span class="hljs-keyword">if</span> (path.endsWith(<span class="hljs-string">'.md'</span>)) {
    <span class="hljs-keyword">return</span> sendMarkdown(req, rsp, path)
  }
  body = fs.readFileSync(path)
  rsp.writeHead(<span class="hljs-number">200</span>,
    {
      <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'text/plain'</span>,
      <span class="hljs-string">'Content-Length'</span>: Buffer.byteLength(body)
    }
  )
  rsp.end(body)
}
</code></pre>
<h2 id="commence-markdownification">Commence Markdownification</h2>
<p>Ah, Internet, how I love thee! There are plenty of libraries which will convert Markdown into HTML, all just a search-engine away. Let's add one to our project:</p>
<pre><code class="lang-Console">npm install --save showdown
</code></pre>
<p>And write our <code>sendMarkdown()</code> function:</p>
<pre><code class="lang-JavaScript"><span class="hljs-keyword">const</span> showdown  = <span class="hljs-built_in">require</span>(<span class="hljs-string">'showdown'</span>)
showdown.setFlavor(<span class="hljs-string">'github'</span>)

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sendMarkdown</span>(<span class="hljs-params">req, rsp, path</span>) </span>{
  body = fs.readFileSync(path, <span class="hljs-string">'utf-8'</span>)
  converter = <span class="hljs-keyword">new</span> showdown.Converter()
  html = <span class="hljs-string">'&lt;html&gt;&lt;body&gt;'</span>
    + converter.makeHtml(body)
    + <span class="hljs-string">"&lt;/body&gt;&lt;/html&gt;"</span>;
  rsp.writeHead(<span class="hljs-number">200</span>,
    {
      <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'text/html'</span>,
      <span class="hljs-string">'Content-Length'</span>: Buffer.byteLength(html)
    }
  )
  rsp.end(html)
}
</code></pre>
<p>Since the <code>showdown</code> converter doesn't wrap it's output in <code>html</code> tags, we do that inline, and set the content type correctly.</p>
<h2 id="what-about-all-those-other-file-types">What about all those other file types?</h2>
<p>So far, we've completed a few of the original goals. Let's modify <code>sendFile()</code> to handle more file types, including those we will likely encounter along the way.</p>
<pre><code class="lang-JavaScript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sendFile</span>(<span class="hljs-params">req, rsp, path</span>) </span>{
  <span class="hljs-keyword">let</span> ctype
  ext = path.split(<span class="hljs-string">'/'</span>).slice(<span class="hljs-number">-1</span>)[<span class="hljs-number">0</span>].split(<span class="hljs-string">'.'</span>).slice(<span class="hljs-number">-1</span>)[<span class="hljs-number">0</span>]
  <span class="hljs-keyword">switch</span> (ext) {
    <span class="hljs-keyword">case</span> <span class="hljs-string">'css'</span>:
    <span class="hljs-keyword">case</span> <span class="hljs-string">'html'</span>:
      ctype = <span class="hljs-string">`text/<span class="hljs-subst">${ext}</span>`</span>; <span class="hljs-keyword">break</span>
    <span class="hljs-keyword">case</span> <span class="hljs-string">'gif'</span>:
    <span class="hljs-keyword">case</span> <span class="hljs-string">'jpeg'</span>:
    <span class="hljs-keyword">case</span> <span class="hljs-string">'png'</span>:
      ctype = <span class="hljs-string">`image/<span class="hljs-subst">${ext}</span>`</span>; <span class="hljs-keyword">break</span>
    <span class="hljs-keyword">case</span> <span class="hljs-string">'jpg'</span>:
      ctype = <span class="hljs-string">'image/jpeg'</span>; <span class="hljs-keyword">break</span>
    <span class="hljs-keyword">case</span> <span class="hljs-string">'js'</span>:
      ctype = <span class="hljs-string">'text/javascript'</span>; <span class="hljs-keyword">break</span>
    <span class="hljs-keyword">case</span> <span class="hljs-string">'md'</span>:
      <span class="hljs-keyword">return</span> sendMarkdown(req, rsp, path)
    <span class="hljs-attr">default</span>:
      ctype = <span class="hljs-string">'text/plain'</span>
  }
  body = fs.readFileSync(path)
  rsp.writeHead(<span class="hljs-number">200</span>,
    {
      <span class="hljs-string">'Content-Type'</span>: ctype,
      <span class="hljs-string">'Content-Length'</span>: Buffer.byteLength(body)
    }
  )
  rsp.end(body)
}
</code></pre>
<p>We capture the file extension (if any), and add a <code>switch</code> statement to map the usual gang of file extensions (with a special case for <code>.jpg</code>) the the correct MIME type. Yes, there are libraries for this, but this suffices for our very simple needs, and keeps the project lightweight.</p>
<p>Now we can serve up files which are named explicitly, and we are converting Markdown to HTML, but we are not handling directory indexes or mapping an <code>.html</code> URL to a corresponding <code>.md</code> file.</p>
<h2 id="handling-directories-and-non-existent-paths">Handling directories and non-existent paths</h2>
<p>If you visit <code>http://localhost:3000/</code>, the request will time out, because we're not sending any data unless we find a file, and <code>/</code> is NOT a file, per-se. What we really want is to serve an index file (either <code>index.html</code> or <code>index.md</code>) when a request maps to a directory. And we want to send back a 404 error if we can't find the requested file or the index for a directory.</p>
<p>Let's modify <code>resolvePath()</code> a bit:</p>
<pre><code class="lang-JavaScript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">resolvePath</span>(<span class="hljs-params">req, rsp, path</span>) </span>{
  path = pathlib.normalize(path)
  <span class="hljs-keyword">if</span> (fs.existsSync(path)) {
    <span class="hljs-keyword">if</span> (fs.lstatSync(path).isFile())  {
      <span class="hljs-keyword">return</span> sendFile(req, rsp, path)
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (fs.lstatSync(path).isDirectory()) {
      <span class="hljs-keyword">return</span> resolvePath(req, rsp, <span class="hljs-string">`<span class="hljs-subst">${path.replace(<span class="hljs-regexp">/\/$/</span>, <span class="hljs-string">''</span>)}</span>/index.html`</span>)
    }
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-keyword">return</span> sendError(req, rsp, <span class="hljs-number">404</span>))
  }
}
</code></pre>
<p>First, we normalize the path to account for segments like <code>../../stuff</code> and other variations.</p>
<p>Then, if the path exists and is a file, we call <code>sendFile()</code> as before. If it's a directory, we call <code>resolvePath()</code> (recursively) for a theoretical <code>index.html</code> in that directory. We do a little regex work to ensure have exactly one '/' between the original URL and <code>index.html</code>. If the path doesn't exits, we'll call <code>sendError()</code> to send back a 404-Not-Found.  Probably should write that, too:</p>
<pre><code class="lang-JavaScript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sendError</span>(<span class="hljs-params">req, rsp, code</span>) </span>{
  messages = {
    <span class="hljs-number">404</span>: <span class="hljs-string">"Not Found"</span>
  }
  rsp.writeHead(code, {<span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'text/html'</span>})
  rsp.end(<span class="hljs-string">`&lt;html&gt;&lt;body&gt;&lt;h1&gt;<span class="hljs-subst">${code}</span> <span class="hljs-subst">${messages[code]}</span>&lt;/h1&gt;&lt;/body&lt;/html&gt;`</span>)
}
</code></pre>
<h2 id="serving-markdown-where-html-was-requested">Serving Markdown where HTML was requested</h2>
<p>If the URL ends in <code>.html</code>, but no such file exits, we should look for a file with the same name, but ending in <code>.md</code>. This, once again, is handled by <code>resolvePath()</code>:</p>
<pre><code class="lang-JavaScript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">resolvePath</span>(<span class="hljs-params">req, rsp, path</span>) </span>{
  path = pathlib.normalize(path)
  <span class="hljs-keyword">if</span> (fs.existsSync(path)) {
...
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (path.endsWith(<span class="hljs-string">'.html'</span>)) {
    <span class="hljs-comment">// Maybe there's a corresponding .md file we can send instead?</span>
    <span class="hljs-keyword">return</span> resolvePath(req, rsp, path.slice(<span class="hljs-number">0</span>, <span class="hljs-number">-5</span>) + <span class="hljs-string">".md"</span>)
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-keyword">return</span> sendError(req, rsp, <span class="hljs-number">404</span>)
  }
}
</code></pre>
<p>We've wedged another if-clause between "the path exits" and "send a not-found" which will, if the path ends in <code>.html</code>, call <code>resolvePath()</code> with the updated pathname.</p>
<h2 id="all-the-boxes-ticked">All the boxes ticked!</h2>
<p>That wraps it up. We've completed all of the original objectives, to wit:</p>
<ul>
<li>We have an HTTP server</li>
<li>which sends files from the current directory (with the correct MIME-types, mostly)</li>
<li>and processes Markdown into HTML on the fly</li>
<li>and if a request for an <code>.html</code> resource comes in but no such file exists, it tries the corresponding <code>.md</code> file</li>
<li>and if the URL looks like a directory, it serves up <code>index.html</code> (which could actually be <code>index.md</code>)</li>
<li>and gives meaningful errors as needed (just 404, but they're meaningful!)</li>
</ul>
<p>You may recall I mentioned adding features to <code>resolveURL()</code>, but those aren't covered here. You can find a more complete version of this code in the <a target="_blank" href="https://github.com/tdesposito/EH-mdServer">eh-mdserver GitHub project</a>, which has a few more tricks up it's sleeve.</p>
<p>The repository also contains the <a target="_blank" href="https://github.com/tdesposito/EH-mdServer/blob/main/blog-version.js">version we built here</a> for your kind consideration.</p>
<p>As always, your constructive comments are very welcome!</p>
]]></content:encoded></item><item><title><![CDATA[Events: Web Components Phone Home]]></title><description><![CDATA[This is (probably) the final installment in my series on Web Components, which you should totally check out if you haven't already, not only because I wanted to use the phrase "totally check out" in print, but also because those previous couple of ar...]]></description><link>https://toddesposito.com/events-web-components-phone-home</link><guid isPermaLink="true">https://toddesposito.com/events-web-components-phone-home</guid><category><![CDATA[Web Development]]></category><category><![CDATA[Web Components]]></category><category><![CDATA[webdev]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Todd Esposito]]></dc:creator><pubDate>Wed, 15 Sep 2021 23:07:06 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1631707697683/WQQ1MO6fm.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This is (probably) the final installment in my <a target="_blank" href="/series/web-components">series on Web Components</a>, which you should totally check out if you haven't already, not only because I wanted to use the phrase "totally check out" in print, but also because those previous couple of articles lay the groundwork for understanding this one.</p>
<h1 id="obligatory-recap">Obligatory Recap</h1>
<p>In the previous articles, we've built a reasonably capable component which let's us add a "loading" screen to a web page. You add the component to the page in a few lines of code:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"eh-loading-modal.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">main</span>&gt;</span>some content here<span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">eh-loading-modal</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">eh-loading-modal</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
     <span class="hljs-keyword">const</span> loader = <span class="hljs-built_in">document</span>.getElementsByTagName(<span class="hljs-string">'eh-loading-modal'</span>)[<span class="hljs-number">0</span>]
     loader.show()
     <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> loader.hide(), <span class="hljs-number">5000</span>)
  </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>We include the components source in the <code>&lt;head&gt;</code>, drop the component on the page with the <code>&lt;eh-loading-modal&gt;</code> tag, and manipulate it with JavaScript.</p>
<h1 id="huh-events">Huh? Events?</h1>
<p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/Events">Events</a> are signals you can listen for and respond to in your code. For example, you've probably seen code like:</p>
<pre><code class="lang-JavaScript">node.AddEventListener(<span class="hljs-string">'click'</span>, somefunction);
</code></pre>
<p>This tells your browser that when a users "clicks" on that particular node, it should run your <code>somefunction()</code> code. DOM nodes "emit events" during interactions with the person on the other side of the screen, or when other "interesting" things happen. Different interactions result in different events, of course, and there are a handful of <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/Events#event_index">standardized events</a>. Ok, it's LARGE handful.</p>
<p>You can, in your code, emit these standard events or create your own custom events.</p>
<h1 id="emitting-an-event">Emitting an Event</h1>
<p>Our component isn't a very good candidate for an event source. It intentionally doesn't interact with the user, and it doesn't much matter if the window is resized, or the battery goes low. But lack of solid reasons never stopped me before, so ... onward!</p>
<p>We're going to have our component emit an event whenever it is shown or hidden. I know, it's unlikely such a feature would be useful, but, like the plot in every RomCom ever put to film, let's just pretend it makes sense.</p>
<p>First, let's revisit our component's code, specifically the <code>show()</code> and <code>hide()</code> methods:</p>
<pre><code class="lang-JavaScript"><span class="hljs-built_in">window</span>.customElements.define(<span class="hljs-string">'eh-loading-modal'</span>,
  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">EhLoadingModal</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
...
    show() { <span class="hljs-built_in">this</span>.setAttribute(<span class="hljs-string">"visible"</span>, <span class="hljs-string">""</span>) }
    hide() { <span class="hljs-built_in">this</span>.removeAttribute(<span class="hljs-string">"visible"</span>) }
...
</code></pre>
<p>If you've forgotten what these are doing, please see <a target="_blank" href="https://toddesposito.com/slots-and-properties-and-attributes-oh-my">Slots and Properties and Attributes, Oh My!</a>. Basically, call <code>show()</code> to show the modal, and <code>hide()</code> to hide it. </p>
<p>We want some events  to come out of the component when it goes from hidden to showing, and from showing to hidden (again, as in a RomCom, it's best not to question it), so let's revise those methods slightly:</p>
<pre><code class="lang-JavaScript"><span class="hljs-built_in">window</span>.customElements.define(<span class="hljs-string">'eh-loading-modal'</span>,
  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">EhLoadingModal</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
...
    show() { 
      <span class="hljs-built_in">this</span>.setAttribute(<span class="hljs-string">"visible"</span>, <span class="hljs-string">""</span>)
      <span class="hljs-built_in">this</span>.dispatchEvent(<span class="hljs-keyword">new</span> CustomEvent(<span class="hljs-string">"show"</span>))
    }
    hide() {
      <span class="hljs-built_in">this</span>.removeAttribute(<span class="hljs-string">"visible"</span>)
      <span class="hljs-built_in">this</span>.dispatchEvent(<span class="hljs-keyword">new</span> CustomEvent(<span class="hljs-string">"hide"</span>))
    }
...
</code></pre>
<p>Pretty straightforward. We do the work needed to effect the transition we want, and then we call <code>dispatchEvent()</code> to send out a custom Event. The <code>dispatchEvent()</code> method is inherited from <code>HTMLElement</code>, so we don't need to worry about it.</p>
<p>Now, let's update our POC page, crack open the Console window in your favorite browser and test it out:</p>
<pre><code class="lang-HTML"><span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"eh-loading-modal.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">main</span>&gt;</span>some content here<span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">eh-loading-modal</span> <span class="hljs-attr">visible</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Please Wait<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>We're loading your report. This could take a moment.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">eh-loading-modal</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
     <span class="hljs-keyword">const</span> loader = <span class="hljs-built_in">document</span>.getElementsByTagName(<span class="hljs-string">'eh-loading-modal'</span>)[<span class="hljs-number">0</span>]
     loader.addEventListener(<span class="hljs-string">'show'</span>, <span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Modal is showing"</span>))
     loader.addEventListener(<span class="hljs-string">'hide'</span>, <span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Modal is hidding"</span>))
     loader.show()
     <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> loader.hide(), <span class="hljs-number">5000</span>)
  </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>You should get something very like this in your Console:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631744852029/ZNfd6Vhgw.png" alt="console-screenshot.png" /></p>
<h1 id="why-not-standard-events">Why not "Standard" Events?</h1>
<p>When you use <code>addEventListener()</code>, you specify a string naming the event, which matches what we send via <code>dispatchEvent()</code>. And, as shown above, you create a <code>CustomEvent</code> object to carry your event. But you can ALSO use a "standard" event:</p>
<pre><code>        <span class="hljs-keyword">this</span>.dispatchEvent(new Event(<span class="hljs-string">"change"</span>))
</code></pre><p>Events like "change" and "click" are in common usage. We all understand what they mean in the context of standard HTML nodes, like an <code>&lt;input&gt;</code> or a <code>&lt;button&gt;</code> and so if our component behaves in a similar way, it should probably emit the same events.</p>
<p>Let's make an adjustment to our component to reflect that.</p>
<pre><code class="lang-JavaScript"><span class="hljs-built_in">window</span>.customElements.define(<span class="hljs-string">'eh-loading-modal'</span>,
  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">EhLoadingModal</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
...
    show() { 
      <span class="hljs-built_in">this</span>.setAttribute(<span class="hljs-string">"visible"</span>, <span class="hljs-string">""</span>)
      <span class="hljs-built_in">this</span>.dispatchEvent(<span class="hljs-keyword">new</span> Event(<span class="hljs-string">"change"</span>))
    }
    hide() {
      <span class="hljs-built_in">this</span>.removeAttribute(<span class="hljs-string">"visible"</span>)
      <span class="hljs-built_in">this</span>.dispatchEvent(<span class="hljs-keyword">new</span> Event(<span class="hljs-string">"change"</span>))
    }
...
</code></pre>
<p>And our POC page:</p>
<pre><code class="lang-HTML">...
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
     <span class="hljs-keyword">const</span> loader = <span class="hljs-built_in">document</span>.getElementsByTagName(<span class="hljs-string">'eh-loading-modal'</span>)[<span class="hljs-number">0</span>]
     loader.addEventListener(<span class="hljs-string">'change'</span>, <span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Modal is <span class="hljs-subst">${loader.visible ? <span class="hljs-string">'showing'</span> : <span class="hljs-string">'hidding'</span><span class="hljs-string">`))
     loader.show()
     setTimeout(() =&gt; loader.hide(), 5000)
  </span></span></span></span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
...
</code></pre>
<p>Same result, and we're now using an event everybody already knows.  And since we often place such event listeners higher up in the DOM (such as on the document), let's see what happens when we do this:</p>
<pre><code class="lang-HTML">...
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
     ...
     document.addEventListener(<span class="hljs-string">'change'</span>, <span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Modal is <span class="hljs-subst">${loader.visible ? <span class="hljs-string">'showing'</span> : <span class="hljs-string">'hidding'</span><span class="hljs-string">`))
     ...
  </span></span></span></span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
...
</code></pre>
<h1 id="oh-event-where-art-thou">Oh Event, where art thou?</h1>
<p>So the page will show the modal, then 10 seconds later hide it, as you'd hope, but our console messages have gone. Why?</p>
<p>You'll recall -- especially if you go back to read <a target="_blank" href="https://toddesposito.com/component-styling-for-fun-and-profit">Component Styling For Fun and Profit</a> -- that there is a boundary between the page's DOM and the "mini-DOM" (shadowRoot) which comprises a component on that page. Things like CSS selector and selector queries won't cross that boundary. Same for Events, <strong>except</strong> when we tell the Event that it SHOULD cross that boundary:</p>
<pre><code class="lang-JavaScript"><span class="hljs-built_in">window</span>.customElements.define(<span class="hljs-string">'eh-loading-modal'</span>,
...
    show() { 
      <span class="hljs-built_in">this</span>.setAttribute(<span class="hljs-string">"visible"</span>, <span class="hljs-string">""</span>)
      <span class="hljs-built_in">this</span>.dispatchEvent(<span class="hljs-keyword">new</span> Event(<span class="hljs-string">"change"</span>, {<span class="hljs-attr">bubbles</span>: <span class="hljs-literal">true</span>}))
    }
...
</code></pre>
<p>And now we get our events back again.</p>
<h1 id="thats-a-wrap">That's a wrap</h1>
<p>There's SO much more to explore, but I think, for now, we're done. I hope you've enjoyed this series, and learned something. Please feel free to share your constructive comments!</p>
<p>And also feel free to check out and use this component, and a (slowly) growing collection of others I find myself using more and more frequently, at my <a target="_blank" href="https://tdesposito.github.io/EH-WebComponents">EH-WebComponents Demo</a> site, and find the source <a target="_blank" href="https://github.com/tdesposito/EH-WebComponents">on GitHub</a>!</p>
]]></content:encoded></item><item><title><![CDATA[Component Styling For Fun and Profit]]></title><description><![CDATA[In the continuing saga of our Web Component building journey, we now turn our attention to allowing our web components to be styled. In case you missed it, we've been building a "loading modal" web component, and we've added some customizability via ...]]></description><link>https://toddesposito.com/component-styling-for-fun-and-profit</link><guid isPermaLink="true">https://toddesposito.com/component-styling-for-fun-and-profit</guid><category><![CDATA[Web Development]]></category><category><![CDATA[webdev]]></category><category><![CDATA[Web Components]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Todd Esposito]]></dc:creator><pubDate>Mon, 23 Aug 2021 15:19:59 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1629731964126/qHaVY7DSj.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In the continuing saga of our Web Component building journey, we now turn our attention to allowing our web components to be styled. In case you missed it, we've been <a target="_blank" href="/building-a-loading-modal-component">building a "loading modal" web component</a>, and we've added some <a target="_blank" href="slots-and-properties-and-attributes-oh-my">customizability via slots, properties and attributes</a> already.</p>
<p>This component gives us a nice, simple way to drop a commonly-used feature into a web page. Load the component via <code>&lt;script&gt;</code> tag, add a <code>&lt;eh-loading-modal&gt;</code> tag somewhere on the page, and when needed, call the <code>.show()</code> and <code>.hide()</code> methods on the node.</p>
<p>What's still missing (and was promised in the previous article) is the ability to change the look of the component.</p>
<h2 id="lets-add-some-style">Let's add some style</h2>
<p>Before we begin, let's review the inline-CSS of our component.</p>
<pre><code>      this.shadowRoot.innerHTML = `
      <span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
        <span class="hljs-selector-id">#loading</span> {
          <span class="hljs-attribute">background-color</span>: gray;
          <span class="hljs-attribute">height</span>: <span class="hljs-number">100%</span>;
          <span class="hljs-attribute">left</span>: <span class="hljs-number">0</span>;
          <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0</span>;
          <span class="hljs-attribute">position</span>: fixed;
          <span class="hljs-attribute">top</span>: <span class="hljs-number">0</span>;
          <span class="hljs-attribute">visibility</span>: hidden;
          <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>;
          <span class="hljs-attribute">z-index</span>: <span class="hljs-number">1</span>;
        }
        <span class="hljs-selector-id">#loading</span><span class="hljs-selector-class">.visible</span> {
          <span class="hljs-attribute">opacity</span>: <span class="hljs-number">97%</span>;
          <span class="hljs-attribute">visibility</span>: visible;
          <span class="hljs-attribute">transition</span>: visibility <span class="hljs-number">0s</span> linear <span class="hljs-number">0s</span>, opacity .<span class="hljs-number">25s</span> <span class="hljs-number">0s</span>;
        }
        <span class="hljs-selector-id">#loading</span> <span class="hljs-selector-class">.message</span> {
          <span class="hljs-attribute">height</span>: <span class="hljs-number">100%</span>;
          <span class="hljs-attribute">margin</span>: <span class="hljs-number">20px</span> <span class="hljs-number">2px</span>;
          <span class="hljs-attribute">padding</span>: <span class="hljs-number">2px</span> <span class="hljs-number">16px</span>;
          <span class="hljs-attribute">position</span>: relative;
          <span class="hljs-attribute">text-align</span>: center;
          <span class="hljs-attribute">top</span>: <span class="hljs-number">40%</span>;
          <span class="hljs-attribute">vertical-align</span>: middle;
        }
      </span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
...
</code></pre><p>There are several hard-wired assumptions here about how the modal will look. It starts right at the top, with <code>background-color: gray;</code> and continues on with <code>opacity: 97%</code> under the <code>.visible</code> selector. These two lines give us the "mask" which ocludes the page when the component is visible. But what if we didn't want a 97% gray mask? What if we wanted sea-foam green?</p>
<p>The first thing you'd likely think of is something of this sort:</p>
<pre><code class="lang-CSS"><span class="hljs-selector-tag">eh-loading-modal</span> &gt; <span class="hljs-selector-tag">div</span> {
  <span class="hljs-attribute">background-color</span>: <span class="hljs-built_in">rgb</span>(<span class="hljs-number">113</span> <span class="hljs-number">238</span> <span class="hljs-number">184</span>);
}
</code></pre>
<p>Alas, this won't work. At least not as you expect, but more on that below. You may recall from the <a target="_blank" href="/building-a-loading-modal-component">first article in this series</a> that a component has a "mini-DOM" (shadowRoot) which sits inside the document's DOM. The issue here is that CSS selectors don't cross those boundries. Which makes sense, if you consider that our component is using IDs and class names for it's own purposes, and things could break badly if some arbitrary CSS from the page changed how our component worked, or we had multiple different components on the page, each with CSS which interferred with each other and/or the page itself. So 10 out of 10 to the web standards folks on that.</p>
<p>So how do we do it? More properties? Slots? Pan-fried salmon?</p>
<h2 id="css-custom-properties-to-the-rescue">CSS custom properties to the rescue!</h2>
<p>Nope, we'll leverage the fact that CSS custom properties DO cross those DOM borders!</p>
<p>Let's begin with our mask color:</p>
<pre><code class="lang-CSS">      <span class="hljs-selector-tag">this</span><span class="hljs-selector-class">.shadowRoot</span><span class="hljs-selector-class">.innerHTML</span> = `
      &lt;<span class="hljs-selector-tag">style</span>&gt;
        <span class="hljs-selector-id">#loading</span> {
          <span class="hljs-attribute">background-color</span>: <span class="hljs-built_in">var</span>(--mask-background-color, gray);
...
        }
        <span class="hljs-selector-id">#loading</span><span class="hljs-selector-class">.visible</span> {
          <span class="hljs-attribute">opacity</span>: <span class="hljs-built_in">var</span>(--mask-opacity, <span class="hljs-number">97%</span>);
...
        }
      &lt;/<span class="hljs-selector-tag">style</span>&gt;
...
</code></pre>
<p>The <code>var()</code> function is replaced with EITHER the value of the named custom property, if it's defined, or the value after the comma, if not. Because we've put our original values in as fallbacks to the custom properies, nothing will (yet) change in our POC page, which, you'll recall, is:</p>
<pre><code class="lang-HTML"><span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"eh-loading-modal.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">main</span>&gt;</span>some content here<span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">eh-loading-modal</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">eh-loading-modal</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
     <span class="hljs-keyword">const</span> loader = <span class="hljs-built_in">document</span>.getElementsByTagName(<span class="hljs-string">'eh-loading-modal'</span>)[<span class="hljs-number">0</span>]
     loader.show()
     <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> loader.hide(), <span class="hljs-number">5000</span>)
  </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>Let's sea-foam that baby up by adding a <code>&lt;style&gt;</code> block to the <code>&lt;head&gt;</code> of our page</p>
<pre><code class="lang-HTML"><span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"eh-loading-modal.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
    <span class="hljs-selector-tag">eh-loading-modal</span> {
      <span class="hljs-attribute">--mask-background-color</span>:  <span class="hljs-built_in">rgb</span>(<span class="hljs-number">113</span> <span class="hljs-number">238</span> <span class="hljs-number">184</span>);
      <span class="hljs-attribute">--mask-opacity</span>: <span class="hljs-number">88%</span>;
    }
  </span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
...
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>Reload the POC and bask in the glorious customization!</p>
<h2 id="styling-nodes-in-a-slot">Styling nodes in a <code>slot</code></h2>
<p>Above, I suggested that a selector like <code>eh-loading-modal &gt; div {</code> would work, but not as expected. If you are expecting that selector to pick up the first child <code>div</code> INSIDE the component's shadowRoot, you will be disappointed; selectors don't cross that boundry. This is why we need to use CSS custom properties above.</p>
<p>However, if you put content between the start and end tags of the component (in one or more <code>slot</code>s), THOSE nodes ARE selectable via CSS. To see this in action try the following in your POC page:</p>
<pre><code class="lang-HTML"><span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
    <span class="hljs-selector-tag">eh-loading-modal</span> &gt; <span class="hljs-selector-tag">div</span> {
      <span class="hljs-attribute">color</span>: blue;
    }
...
  </span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
...
  <span class="hljs-tag">&lt;<span class="hljs-name">eh-loading-modal</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>This will be styled<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">eh-loading-modal</span>&gt;</span>
...
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>Styles applied to slots by the component's CSS will be overridden by the page's CSS, so adding custom properties to slotted elements isn't strictly neccessary.</p>
<h2 id="styles-of-style">Styles of style</h2>
<p>There are several ways to apply CSS rules to components and slots within components, including in a separate css file, in a <code>&lt;style&gt;</code> tag in either the <code>&lt;head&gt;</code> or the <code>&lt;body&gt;</code> or, interstingly, inside the component's tag.</p>
<p>Consider this:</p>
<pre><code class="lang-HTML"><span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"eh-loading-modal.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
    <span class="hljs-selector-tag">eh-loading-modal</span> {
      <span class="hljs-attribute">--mask-background-color</span>: <span class="hljs-built_in">rgb</span>(<span class="hljs-number">113</span> <span class="hljs-number">238</span> <span class="hljs-number">184</span>);
      <span class="hljs-attribute">--mask-opacity</span>: <span class="hljs-number">88%</span>;
    }
  </span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">main</span>&gt;</span>some content here<span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">eh-loading-modal</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
        * {
          <span class="hljs-attribute">--content-text-align</span>: right;
        }
     </span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>This will be styled<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">eh-loading-modal</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
     <span class="hljs-keyword">const</span> loader = <span class="hljs-built_in">document</span>.getElementsByTagName(<span class="hljs-string">'eh-loading-modal'</span>)[<span class="hljs-number">0</span>]
     loader.show()
     <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> loader.hide(), <span class="hljs-number">5000</span>)
  </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<h2 id="next-steps">Next steps</h2>
<p>So now we have a component we can customize, interact with and style. Next time out, we'll discuss event handling, both internally to a component and between the component and the page.</p>
]]></content:encoded></item><item><title><![CDATA[Slots and Properties and Attributes, Oh My!]]></title><description><![CDATA[This article is the second in my series on building Web Components, and builds on what I started in Building A Loading Modal Component. If you haven't seen that one yet, you may want to give it a quick read to catch yourself up.
Making our component ...]]></description><link>https://toddesposito.com/slots-and-properties-and-attributes-oh-my</link><guid isPermaLink="true">https://toddesposito.com/slots-and-properties-and-attributes-oh-my</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Web Components]]></category><dc:creator><![CDATA[Todd Esposito]]></dc:creator><pubDate>Wed, 04 Aug 2021 22:07:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1628103581781/USa463VNa.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This article is the second in my <a target="_blank" href="https://toddesposito.com/series/web-components">series on building Web Components</a>, and builds on what I started in <a target="_blank" href="https://toddesposito.com/building-a-loading-modal-component">Building A Loading Modal Component</a>. If you haven't seen that one yet, you may want to give it a quick read to catch yourself up.</p>
<h2 id="making-our-component-more-flexible">Making our component more flexible</h2>
<p>So we have a working component. Great. But, as written, it isn't flexible enough to be of use as a general purpose tool.</p>
<p>We'll tackle this in a couple steps. In this article, we'll dive into three aspects of Web Components: </p>
<ul>
<li><strong>Slots</strong> allow us to put content inside our component</li>
<li><strong>Attributes</strong> allow us to configure our component in the HTML tag</li>
<li><strong>Properties</strong> allow us to interact with our component in JavaScript</li>
</ul>
<p>In an upcoming article, we'll discuss how to alter the styling of the component. Patience is a virtue.</p>
<h2 id="first-round-of-improvements">First round of improvements</h2>
<p>Let's review our "proof of concept" page:</p>
<pre><code class="lang-HTML"><span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"eh-loading-modal.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">main</span>&gt;</span>some content here<span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">eh-loading-modal</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">eh-loading-modal</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
     <span class="hljs-keyword">const</span> loader = <span class="hljs-built_in">document</span>.getElementsByTagName(<span class="hljs-string">'eh-loading-modal'</span>)[<span class="hljs-number">0</span>]
     loader.show()
     <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> loader.hide(), <span class="hljs-number">5000</span>)
  </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>As you'll recall from the previous article, we load the Component via <code>&lt;script&gt;</code> tag, add it to our page via <code>&lt;eh-loading-modal&gt;</code> tag, and then show (and later hide) it via the JavaScript at the end of the page. </p>
<p>Our Component, so far, is:</p>
<pre><code class="lang-JavaScript"><span class="hljs-built_in">window</span>.customElements.define(<span class="hljs-string">'eh-loading-modal'</span>,
  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">EhLoadingModal</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
    <span class="hljs-keyword">constructor</span>() {
      <span class="hljs-built_in">super</span>()
      <span class="hljs-built_in">this</span>.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">"open"</span> })
      <span class="hljs-built_in">this</span>.shadowRoot.innerHTML = <span class="hljs-string">`
      &lt;style&gt;
        #loading {
          background-color: gray;
          height: 100%;
          left: 0;
          opacity: 0;
          position: fixed;
          top: 0;
          visibility: hidden;
          width: 100%;
          z-index: 1;
        }
        #loading.visible {
          opacity: 97%;
          visibility: visible;
          transition: visibility 0s linear 0s, opacity .25s 0s;
        }
        #loading .message {
          height: 100%;
          margin: 20px 2px;
          padding: 2px 16px;
          position: relative;
          text-align: center;
          top: 40%;
          vertical-align: middle;
        }
      &lt;/style&gt;
      &lt;div id="loading"&gt;
        &lt;div class="message"&gt;
          &lt;p&gt;Loading... &lt;/p&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      `</span>
    }
    show() {
      <span class="hljs-built_in">this</span>.shadowRoot.getElementById(<span class="hljs-string">'loading'</span>).classList.add(<span class="hljs-string">"visible"</span>)
    }
    hide() { 
      <span class="hljs-built_in">this</span>.shadowRoot.getElementById(<span class="hljs-string">'loading'</span>).classList.remove(<span class="hljs-string">"visible"</span>)
    }
})
</code></pre>
<h2 id="slots">Slots</h2>
<p>I'm sure you noticed that the component, when shown, always puts the word "Loading..." in the center of the page on a gray background. Swell. But maybe you want something else, like "Please Wait" or a nice animated image. It sure would suck to have to re-write our component every time we wanted a different message, especially since that's EXACTLY what we're trying to avoid doing in the first place.  <strong>Slots</strong> solve this problem.</p>
<p>Let's adjust our component code to add a "slot" in place of the paragraph which contains the original message. In the <code>constructor()</code> we'll update:</p>
<p><code>&lt;p&gt;Loading...&lt;/p&gt;</code></p>
<p>to read:</p>
<p><code>&lt;slot&gt;Loading...&lt;/slot&gt;</code></p>
<p>The <code>&lt;slot&gt;</code> tag identifies a place where we can slot-in (get it?) other content. If you change the component as above, and re-load your POC page, nothing will have changed.</p>
<p>BUT, when we update our POC page with:</p>
<pre><code><span class="hljs-tag">&lt;<span class="hljs-name">eh-loading-modal</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Please Wait<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>We're loading your report. This could take a moment.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/static/spinner.webp"</span>/&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">eh-loading-modal</span>&gt;</span>
</code></pre><p>we get a different result.  Have fun playing with that, keeping in mind that any valid HTML can go inside those <code>&lt;eh-loading-modal&gt;</code> tags, and it will render much as you'd expect.</p>
<h3 id="named-slots">Named slots</h3>
<p>It's important to note that slots can be named and then referenced by name. Let's take an example building on our work so far.  If we were to update our component's <code>innerHTML</code> like this:</p>
<pre><code>...
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"message"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">slot</span>&gt;</span>Loading... <span class="hljs-tag">&lt;/<span class="hljs-name">slot</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">slot</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"footer"</span>&gt;</span>(this is a footer)<span class="hljs-tag">&lt;/<span class="hljs-name">slot</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
...
</code></pre><p>we'd now have a default "footer" on our loading page.</p>
<p>Then, to overwrite the content of that footer, we'd update our POC page as:</p>
<pre><code><span class="hljs-tag">&lt;<span class="hljs-name">eh-loading-modal</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Please Wait<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>We're loading your report. This could take a moment.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/static/spinner.webp"</span>/&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">"footer"</span>&gt;</span>This will go in the footer, not the main message area<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">eh-loading-modal</span>&gt;</span>
</code></pre><p>The thing to remember is the <code>&lt;slot&gt;</code> is <code>name</code>d, whereas the element to go into the slot is <code>slot</code>ted.</p>
<h2 id="attributes">Attributes</h2>
<p><strong>Attributes</strong>, part of the HTML specification, are values applied to tags.  For example, in <code>&lt;input type="checkbox" checked&gt;</code> there are two attributes: </p>
<ol>
<li><code>type</code> has the value "checkbox" </li>
<li><code>checked</code> has the implicit value "checked"</li>
</ol>
<p>Maybe now you're thinking</p>
<blockquote>
<p>Why not use an <strong>attribute</strong> rather than a <strong>slot</strong> to set the content of the loading page?</p>
</blockquote>
<p>First, the value of an attribute is a string, which would limit the loading message to text, without the ability to add in images and such.</p>
<p>Beyond that, it's a design choice. I find this:</p>
<pre><code><span class="hljs-tag">&lt;<span class="hljs-name">eh-loading-modal</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"Wait For It..."</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">eh-loading-modal</span>&gt;</span>
</code></pre><p>far less appealing and understandable what I've shown above.</p>
<h3 id="lets-add-an-attribute">Let's add an Attribute!</h3>
<p>Currently, our loading modal doesn't show until we tell it to with JavaScript. What if we want to build a page which immediately shows a loading message, and that message goes away when the page is ready to show whatever data it's been fetching in the background?  Why then, we'd use an Attribute in our HTML, to wit:</p>
<pre><code><span class="hljs-tag">&lt;<span class="hljs-name">eh-loading-modal</span> <span class="hljs-attr">visible</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Please Wait<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>We're loading your report. This could take a moment.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/static/spinner.webp"</span>/&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">"footer"</span>&gt;</span>This will go in the sticky footer, not the main message area<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">eh-loading-modal</span>&gt;</span>
</code></pre><p>See the <code>visible</code> attribute added above? That's going to tell our component it should start off being visible. </p>
<p>Inside our component, the outermost <code>div</code> either has or doesn't have a class of <code>visible</code> to control its visibility. By default, this <code>div</code> does NOT have this class applied. We want our component to react to the presence of the <code>visible</code> attribute by adding the <code>visible</code> class to it when initially rendered.</p>
<h3 id="connectedcallback">connectedCallback</h3>
<p>We have to make a small change to our component to support this new attribute. So far, we've been doing most of our work in our component's <code>constructor()</code> but to enable support for attributes, we're going to refactor much of that work into the <code>connectedCallback()</code> function. The browser calls this function every time it "mounts" a node into the DOM, at which point its attributes are available. Before then, they aren't.</p>
<pre><code class="lang-JavaScript"><span class="hljs-built_in">window</span>.customElements.define(<span class="hljs-string">'eh-loading-modal'</span>,
  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">EhLoadingModal</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
    <span class="hljs-keyword">constructor</span>() {
      <span class="hljs-built_in">super</span>()
      <span class="hljs-built_in">this</span>.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">"open"</span> })
    }
    connectedCallback() {
      <span class="hljs-keyword">const</span> visible = <span class="hljs-built_in">this</span>.hasAttribute(<span class="hljs-string">'visible'</span>) ? <span class="hljs-string">"visible"</span> : <span class="hljs-string">""</span>
      <span class="hljs-built_in">this</span>.shadowRoot.innerHTML = <span class="hljs-string">`
...
    }</span>
</code></pre>
<p>Now we have a <code>const</code> which is EITHER "visible" or "" (empty), depending on the existence of the <code>visible</code> attribute. Let's use it in the <code>innerHTML</code>:</p>
<pre><code class="lang-JavaScript">      <span class="hljs-built_in">this</span>.shadowRoot.innerHTML = <span class="hljs-string">`
        &lt;style&gt;
...
        &lt;/style&gt;
        &lt;div id="loading" class="<span class="hljs-subst">${visible}</span>"&gt;
...</span>
</code></pre>
<p>Let's update our POC page to take advantage of this new behavour:</p>
<pre><code class="lang-HTML"><span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"eh-loading-modal.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">main</span>&gt;</span>some content here<span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">eh-loading-modal</span> <span class="hljs-attr">visible</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Please Wait<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>We're loading your report. This could take a moment.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">eh-loading-modal</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
     <span class="hljs-keyword">const</span> loader = <span class="hljs-built_in">document</span>.getElementsByTagName(<span class="hljs-string">'eh-loading-modal'</span>)[<span class="hljs-number">0</span>]
     <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> loader.hide(), <span class="hljs-number">5000</span>)
  </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<h2 id="properties">Properties</h2>
<p>What if we want to know if the component is currently visible?  <strong>Properties</strong> to the rescue. Properties are component values available to your page's scripts.</p>
<p><strong>PLEASE NOTE:</strong> Properties often look like Attributes, and they can be confused, but they are different things for (often overlapping) purposes.</p>
<p>Let's add this code to the component:</p>
<pre><code class="lang-JavaScript">    <span class="hljs-keyword">get</span> <span class="hljs-title">visible</span>() {
      <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.hasAttribute(<span class="hljs-string">"visible"</span>)
    }
</code></pre>
<p>Now, you can go to your browser's console and type:</p>
<p><code>document.getElementsByTagName('eh-loading-modal')[0].visible</code></p>
<p>You should get back <code>true</code> EVEN IF THE MODAL ISN'T CURRENTLY SHOWING! Why? Because we added the <code>visible</code> <em>attribute</em> to the HTML, so it's ALWAYS there. </p>
<p>How do we address this? We could: </p>
<ul>
<li>ignore the attribute and just maintain some internal state</li>
<li>inspect the classList of the controlling <code>div</code></li>
<li>add and remove the attribute like native DOM nodes do</li>
</ul>
<p>If you toggle an HTML checkbox, you'll notice the <code>checked</code> attribute is added to or removed from the DOM node. We should probably emulate that behavior, so let's go with that last one.</p>
<h3 id="relating-properties-to-attributes">Relating Properties to Attributes</h3>
<p>We need to connect our <code>visible</code> property to our <code>visible</code> attribute, and then use that connection to drive our component's operation.</p>
<p>First, we'll add a "setter" along side our "getter" for <code>visible</code>:</p>
<pre><code class="lang-JavaScript">    <span class="hljs-keyword">get</span> <span class="hljs-title">visible</span>() {
      <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.hasAttribute(<span class="hljs-string">"visible"</span>)
    }
    <span class="hljs-keyword">set</span> <span class="hljs-title">visible</span>(<span class="hljs-params">v</span>) {
      <span class="hljs-keyword">if</span> (v) {
        <span class="hljs-built_in">this</span>.setAttribute(<span class="hljs-string">"visible"</span>, <span class="hljs-string">""</span>)
      } <span class="hljs-keyword">else</span> {
        <span class="hljs-built_in">this</span>.removeAttribute(<span class="hljs-string">"visible"</span>)
      }
    }
</code></pre>
<p>Now, in the console you can type <code>document.getElementsByTagName('eh-loading-modal')[0].visible = true</code> to add the attribute, and <code>document.getElementsByTagName('eh-loading-modal')[0].visible = false</code> to remove it.</p>
<p>And we should probably update <code>show()</code> and <code>hide()</code> to manipulate the attribute, since that's now how we want to effect the behavior:</p>
<pre><code class="lang-JavaScript">    show() { <span class="hljs-built_in">this</span>.setAttribute(<span class="hljs-string">"visible"</span>, <span class="hljs-string">""</span>) }
    hide() { <span class="hljs-built_in">this</span>.removeAttribute(<span class="hljs-string">"visible"</span>) }
</code></pre>
<p>But this still doesn't complete the job, because just toggling the attribute on the node doesn't cause the component to DO anything.  We need to make that attribute "observed" and then attach a callback to the component which implements the intended behavior. Let's add:</p>
<pre><code class="lang-JavaScript">    <span class="hljs-keyword">static</span> <span class="hljs-keyword">get</span> <span class="hljs-title">observedAttributes</span>() {
      <span class="hljs-keyword">return</span> [<span class="hljs-string">"visible"</span>]
    }

    attributeChangedCallback(name, oldValue, newValue) {
      <span class="hljs-keyword">switch</span> (name) {
      <span class="hljs-keyword">case</span> <span class="hljs-string">'visible'</span>:
        <span class="hljs-keyword">if</span> (newValue === <span class="hljs-literal">null</span>) {
          <span class="hljs-built_in">this</span>.shadowRoot.getElementById(<span class="hljs-string">'loading'</span>).classList.remove(<span class="hljs-string">"visible"</span>)
        } <span class="hljs-keyword">else</span> {
          <span class="hljs-built_in">this</span>.shadowRoot.getElementById(<span class="hljs-string">'loading'</span>).classList.add(<span class="hljs-string">"visible"</span>)
        }
        <span class="hljs-keyword">break</span>
      }
    }
</code></pre>
<p>The browser calls <code>attributeChangedCallback()</code> every time one of the attributes indicated by <code>observedAttributes()</code> is changed.</p>
<p>So when our <code>visible</code> attribute is added or removed, <code>attributeChangedCallback()</code> is called with the attribute name, the previoius value of the attribute, and the new value.</p>
<h2 id="better-but-more-to-do">Better, but more to do</h2>
<p>So now we have a loading modal component which we can make visible by default via HTML attribute, fill with arbitrary content in the HTML, and  interrogate with JavaScript to see if it's visible.</p>
<p>That's really nice. But wouldn't it be great to alter the colors, fonts, and layout? Next time, we'll add those capabilities and discuss some design decisions which impact how useful a component will become.</p>
]]></content:encoded></item><item><title><![CDATA[Building a "Loading Modal" Component]]></title><description><![CDATA[Web Component technology is, well, it's delicious bits of Web Candy. Crunchy and hard on the outside, soft and chewy on the inside, delightful throughout.  
Of course, with any technology, you have to ask: Why?
Short Answer: Encapsulation
Encapsulati...]]></description><link>https://toddesposito.com/building-a-loading-modal-component</link><guid isPermaLink="true">https://toddesposito.com/building-a-loading-modal-component</guid><category><![CDATA[Web Development]]></category><category><![CDATA[Web Components]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Todd Esposito]]></dc:creator><pubDate>Thu, 29 Jul 2021 17:58:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1627662736057/zKChHCc4X.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Web Component technology is, well, it's delicious bits of Web Candy. Crunchy and hard on the outside, soft and chewy on the inside, delightful throughout.  </p>
<p>Of course, with any technology, you have to ask: <strong>Why?</strong></p>
<h2 id="short-answer-encapsulation">Short Answer: Encapsulation</h2>
<p>Encapsulation basically means bundling together all the stuff you need to get something done, and hiding the details, so you can just use the thing. We've had encapsulation in software development for decades, and now we finally have it for HTML pages, as well.</p>
<p>As a practical example, think about how many times you've written (or, more likely, copy-and-pasted) the HTML, CSS and JavaScript you need to create a modal "loading" spinner. I've done it at least a dozen times that I can recall. </p>
<h2 id="enough-already-wheres-the-code">Enough already, where's the code?</h2>
<p>The first step in creating my "loading modal" component was identifying the bits I previously copy-pasted, and doing one FINAL copy-paste.</p>
<p>It generally starts with some HTML:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"loading"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"message"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Loading... <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>And corresponding CSS:</p>
<pre><code class="lang-css"><span class="hljs-selector-id">#loading</span> {
  <span class="hljs-attribute">background-color</span>: gray;
  <span class="hljs-attribute">height</span>: <span class="hljs-number">100%</span>;
  <span class="hljs-attribute">left</span>: <span class="hljs-number">0</span>;
  <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0</span>;
  <span class="hljs-attribute">position</span>: fixed;
  <span class="hljs-attribute">top</span>: <span class="hljs-number">0</span>;
  <span class="hljs-attribute">visibility</span>: hidden;
  <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>;
  <span class="hljs-attribute">z-index</span>: <span class="hljs-number">1</span>;
}
<span class="hljs-selector-id">#loading</span><span class="hljs-selector-class">.visible</span> {
  <span class="hljs-attribute">opacity</span>: <span class="hljs-number">97%</span>;
  <span class="hljs-attribute">visibility</span>: visible;
  <span class="hljs-attribute">transition</span>: visibility <span class="hljs-number">0s</span> linear <span class="hljs-number">0s</span>, opacity .<span class="hljs-number">25s</span> <span class="hljs-number">0s</span>;
}
<span class="hljs-selector-id">#loading</span> <span class="hljs-selector-class">.message</span> {
  <span class="hljs-attribute">height</span>: <span class="hljs-number">100%</span>;
  <span class="hljs-attribute">margin</span>: <span class="hljs-number">20px</span> <span class="hljs-number">2px</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">2px</span> <span class="hljs-number">16px</span>;
  <span class="hljs-attribute">position</span>: relative;
  <span class="hljs-attribute">text-align</span>: center;
  <span class="hljs-attribute">top</span>: <span class="hljs-number">40%</span>;
  <span class="hljs-attribute">vertical-align</span>: middle;
}
</code></pre>
<p>And we need a little JavaScript to show and hide the <code>#loading</code> div as needed:</p>
<pre><code class="lang-JavaScript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">showLoading</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'loading'</span>).classList.add(<span class="hljs-string">'visibile'</span>)
}
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">hideLoading</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'loading'</span>).classList.remove(<span class="hljs-string">'visibile'</span>)
}
</code></pre>
<h2 id="first-draft">First Draft</h2>
<p>In order to make that disparate bunch of stuff a single, unified (encapsulated) Web Component, we create a JS file which defines our component:</p>
<pre><code class="lang-JavaScript"><span class="hljs-built_in">window</span>.customElements.define(<span class="hljs-string">'eh-loading-modal'</span>,
  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">EhLoadingModal</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
    <span class="hljs-keyword">constructor</span>() {
      <span class="hljs-built_in">super</span>()
      <span class="hljs-built_in">this</span>.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">"open"</span> })
    }
})
</code></pre>
<p>There you have a basic - and useless - web component. We use the <code>customElements.define</code> function to define the HTML tag we'll use to use our component (<code>&lt;eh-loading-modal&gt;</code>), and the class which will implement the component. We need a constructor (and always call <code>super()</code> to ensure everything sets up correctly), and we need to attach a "Shadow Root" to our component instance. This is basically a tiny DOM which is private to each instance of our component: each time we use that tag on a page, we'll be creating another copy of the tiny DOM inside the page's big DOM.</p>
<p>Now, what do we put into the tiny DOM? Our HTML and CSS, from above, like this:</p>
<pre><code class="lang-JavaScript"><span class="hljs-built_in">window</span>.customElements.define(<span class="hljs-string">'eh-loading-modal'</span>,
  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">EhLoadingModal</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
    <span class="hljs-keyword">constructor</span>() {
      <span class="hljs-built_in">super</span>()
      <span class="hljs-built_in">this</span>.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">"open"</span> })
      <span class="hljs-built_in">this</span>.shadowRoot.innerHTML = <span class="hljs-string">`
      &lt;style&gt;
        #loading {
          background-color: gray;
          height: 100%;
          left: 0;
          opacity: 0;
          position: fixed;
          top: 0;
          visibility: hidden;
          width: 100%;
          z-index: 1;
        }
        #loading.visible {
          opacity: 97%;
          visibility: visible;
          transition: visibility 0s linear 0s, opacity .25s 0s;
        }
        #loading .message {
          height: 100%;
          margin: 20px 2px;
          padding: 2px 16px;
          position: relative;
          text-align: center;
          top: 40%;
          vertical-align: middle;
        }
      &lt;/style&gt;
      &lt;div id="loading"&gt;
        &lt;div class="message"&gt;
          &lt;p&gt;Loading... &lt;/p&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      `</span>
    }
})
</code></pre>
<p>Just copy-paste. Simple.  But now we need to be able to show the thing on demand. Remember I said the <code>shadowRoot</code> is a tiny DOM inside the component. Well, that means that things inside that tiny DOM are encapsulated - we shouldn't try to go mucking about with them directly. We need to add functions to the component which we can call to show and hide the loader. These live inside the component class, and are ALMOST the same as we'd get from copy-pasting the original:</p>
<pre><code class="lang-JavaScript">show() {
  <span class="hljs-built_in">this</span>.shadowRoot.getElementById(<span class="hljs-string">'loading'</span>).classList.add(<span class="hljs-string">"visible"</span>)
}
hide() { 

 <span class="hljs-built_in">this</span>.shadowRoot.getElementById(<span class="hljs-string">'loading'</span>).classList.remove(<span class="hljs-string">"visible"</span>)
}
</code></pre>
<h2 id="eureka">Eureka!</h2>
<p>Now, we can build a really simple proof of concept page:</p>
<pre><code class="lang-HTML"><span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"eh-loading-modal.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">main</span>&gt;</span>some content here<span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">eh-loading-modal</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">eh-loading-modal</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
     <span class="hljs-keyword">const</span> loader = <span class="hljs-built_in">document</span>.getElementsByTagName(<span class="hljs-string">'eh-loading-modal'</span>)[<span class="hljs-number">0</span>]
     loader.show()
     <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> loader.hide(), <span class="hljs-number">5000</span>)
  </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<h1 id="working-but-too-simple">Working, but too simple</h1>
<p>We have a very simple component. It works. But, it's not very flexible. In the next article, we'll talk about that.</p>
]]></content:encoded></item><item><title><![CDATA[When a Spinner Just Won't Do]]></title><description><![CDATA[Streaming is Magic. Ok, not magic, but still pretty neat.
I'm not talking about video or audio streaming here. I'm talking about using streaming to keep a web-app user aware of the progress of a possibly long-running server-side process. Because some...]]></description><link>https://toddesposito.com/when-a-spinner-just-wont-do</link><guid isPermaLink="true">https://toddesposito.com/when-a-spinner-just-wont-do</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Python]]></category><category><![CDATA[Flask Framework]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Todd Esposito]]></dc:creator><pubDate>Mon, 26 Jul 2021 17:18:15 GMT</pubDate><content:encoded><![CDATA[<p>Streaming is Magic. Ok, not magic, but still pretty neat.</p>
<p>I'm not talking about video or audio streaming here. I'm talking about using streaming to keep a web-app user aware of the progress of a possibly long-running server-side process. Because sometimes, a spinner just doesn't cut it.</p>
<h2 id="a-little-background">A Little Background</h2>
<p>I have a client using a cloud-based inventory control system. It works really well, but there are a couple places where the system doesn't line up with my client's processes. Luckily, there is a very nice REST API to work with the system's data.</p>
<p>One of the processes is provided by a legacy system which doesn't know from HTTP, but can export a nice CSV file, so I've connected their business process via a web-app they can kick off as needed, which happens several times a day. Basically, we have to adjust some values in some records based on some factors from the data from the legacy system. You know, business rules.</p>
<h2 id="the-app">The App</h2>
<p>The app (applet? it's a part of a larger collection, so maybe applet) uses the system's API to grab a particular set of records, match them up with the data in the file from the legacy system, and update some subset of the original records with data from the file.  This results in multiple round-trips to the API, and even thought the API is quick, it's not instantaneous. Plus, there's some small amount of data munging and list searching going on, so that takes time, too.</p>
<h2 id="the-problem">The Problem</h2>
<p>On busy days, a single run of this process might need to update upwards of a hundred records at a time, and the process is run by the user (no, it can't be a background task), so they have to sit and wait for it to complete before moving on with their work. And this can happen several times a day.</p>
<p>A simple spinner sitting there for even a few minutes will make my users nervous. They need real-time feedback to be reasured things are going well. Or going at all.</p>
<h2 id="the-solution-streaming">The Solution: Streaming!</h2>
<p>Rather than POST the file from a form and wait for the process to complete and return a status page, we'll leverage the power of streaming to deliver real-time status updates as the process processes.</p>
<h3 id="client-side">Client-Side</h3>
<p>We'll use JS on the app page to send the file. There's an <code>&lt;input type="file"&gt;</code> field on the page, whose <strong><em>change</em></strong> handler POSTs the file to our server-side code:</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onFileSelected</span>(<span class="hljs-params"></span>) </span>{
    fetch(<span class="hljs-string">'/OurProcessHandler'</span>, {
        <span class="hljs-attr">method</span>: <span class="hljs-string">'POST'</span>,
        <span class="hljs-attr">body</span>: <span class="hljs-built_in">this</span>.files[<span class="hljs-number">0</span>],
    })
}
</code></pre>
<p>Nothing unusual there, and this alone doesn't solve the problem. We also need to wait for results from the server, and show the stream of updates to the user. We wait for the fetch Promise to return, then we get the reponse's body (also a Promise), and then pass the "reader" for the body into a function to handle the stream-y nature of the body.</p>
<pre><code class="lang-js">fetch(<span class="hljs-string">'/processHandler'</span>, {
  <span class="hljs-attr">method</span>: <span class="hljs-string">'POST'</span>,
  <span class="hljs-attr">body</span>: <span class="hljs-built_in">this</span>.files[<span class="hljs-number">0</span>],
})
.then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> response.body)
.then(<span class="hljs-function"><span class="hljs-params">body</span> =&gt;</span> updateProgress(body.getReader()))
</code></pre>
<p>The neat part is in that <strong><em>updateProgress</em></strong> function:</p>
<pre><code class="lang-js"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">updateProgress</span>(<span class="hljs-params">reader</span>) </span>{
    <span class="hljs-keyword">const</span> decoder = <span class="hljs-keyword">new</span> TextDecoder(<span class="hljs-string">'utf-8'</span>)
    <span class="hljs-keyword">let</span> done, value;
    <span class="hljs-keyword">var</span> status = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'pre'</span>)
    <span class="hljs-keyword">var</span> container = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'statusMessage'</span>)
    container.appendChild(status)
    <span class="hljs-keyword">while</span> (!done) {
        ({value, done} = <span class="hljs-keyword">await</span> reader.read())
        <span class="hljs-keyword">if</span> (done) {
            <span class="hljs-keyword">var</span> conclude = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'p'</span>)
            conclude.innerHTML = <span class="hljs-string">"Done."</span>
            container.appendChild(conclude)
            <span class="hljs-keyword">return</span> done
        }
        status.innerHTML += decoder.decode(value)
    }
}
</code></pre>
<p>We create an element called <strong><em>status</em></strong>  to append to our <strong><em>#statusMessage</em></strong> element (I'm not showing the HTML because you can probably build it yourself, but it's a <code>&lt;div&gt;</code> in my source). The <strong><em>status</em></strong> element is where the data returned periodically from the server will live. In my case, I'm returning plain text, which renders nicely in a <code>&lt;pre&gt;</code>, but you can use whatever you'd like.</p>
<p>Then, we repeatedly wait for the reader to give us some text, which we append to the <strong><em>status</em></strong> element's <strong><em>innerHTML</em></strong> propery as it arrives. When the reader returns a truthy <strong><em>done</em></strong>, we append a paragraph to  <strong><em>#statusMessage</em></strong> to say so.</p>
<h3 id="server-side">Server Side</h3>
<p>Our server side is build in Python using <a target="_blank" href="https://flask.palletsprojects.com">Flask</a>. We have the normal scafolding around our code in the form of a <strong><em>@route</em></strong> decorator, and we return a Flask <strong><em>Response</em></strong> object. Here we're building the <strong><em>Response</em></strong> object directly, rather than using one of the utility functions, such as <strong><em>render_template</em></strong>.  If we were using the usual templating feature, the entire template would have to be built before <strong>anything</strong> would be sent to the client. Not what we want.</p>
<p>We give our <strong><em>Response</em></strong> object a function, <strong><em>innerHandler</em></strong>, which will generate the data to send to the client, and set the content type to <strong><em>text/event-stream</em></strong> so the client browser understands it should expect the data to be delivered in discrete chunks rather than all at once.</p>
<pre><code class="lang-python"><span class="hljs-meta">@app.route("/processHandler", methods=["POST"])</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">processHandler</span>():</span>
<span class="hljs-meta">    @stream_with_context</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">innerHandler</span>():</span>
        ...
    <span class="hljs-keyword">return</span> Response(innerHandler(), content_type=<span class="hljs-string">'text/event-stream'</span>)
</code></pre>
<p>Note that <strong><em>innerHandler</em></strong> is decorated with <strong><em>@stream_with_context</em></strong>. This decorator, imported from Flask, ensures our request context is always available to <strong><em>innerHandler</em></strong>. Gotta have that.</p>
<p>We'll leverage Python's <strong>yield</strong> keyword to send little bits of text back to the client as we move along our process. Flask will dutifully send these bits to the client as we emit them.</p>
<p>So that our user gets some feedback right away, we'll <strong>yield</strong> a status message <strong>before</strong> we actually pull the request data.</p>
<pre><code class="lang-python"><span class="hljs-meta">@app.route("/processHandler", methods=["POST"])</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">processHandler</span>():</span>
<span class="hljs-meta">    @stream_with_context</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">innerHandler</span>():</span>
        <span class="hljs-keyword">yield</span> <span class="hljs-string">"Examining data...\n"</span>
        filedata = str(request.data, <span class="hljs-string">'utf-8'</span>)
        ...
    <span class="hljs-keyword">return</span> Response(innerHandler(), content_type=<span class="hljs-string">'text/event-stream'</span>)
</code></pre>
<p>We munge each line in the uploaded file to extract a key we can use to get the corresponding data from the API. Since this call may take some time, we signal the user <strong>before</strong> we call the API.</p>
<pre><code class="lang-python"><span class="hljs-meta">@app.route("/processHandler", methods=["POST"])</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">processHandler</span>():</span>
<span class="hljs-meta">    @stream_with_context</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">innerHandler</span>():</span>
        <span class="hljs-keyword">yield</span> <span class="hljs-string">"Examining data...\n"</span>
        filedata = str(request.data, <span class="hljs-string">'utf-8'</span>)
        <span class="hljs-keyword">for</span> line <span class="hljs-keyword">in</span> filedata.split(<span class="hljs-string">"\n"</span>):
            <span class="hljs-comment"># ... snip ... : here we get a key from the line</span>
            <span class="hljs-keyword">yield</span> <span class="hljs-string">f"Retrieving data for record <span class="hljs-subst">{key}</span>..."</span>
            record = getDataFromAPI(key)
            ...
    <span class="hljs-keyword">return</span> Response(innerHandler(), content_type=<span class="hljs-string">'text/event-stream'</span>)
</code></pre>
<p>Finally, update the data in the object, then hit the API again with the updated record. Again, we signal the user <strong>before</strong> we hit the API.</p>
<pre><code class="lang-python"><span class="hljs-meta">@app.route("/processHandler", methods=["POST"])</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">processHandler</span>():</span>
<span class="hljs-meta">    @stream_with_context</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">innerHandler</span>():</span>
        <span class="hljs-keyword">yield</span> <span class="hljs-string">"Examining data...\n"</span>
        filedata = str(request.data, <span class="hljs-string">'utf-8'</span>)
        <span class="hljs-keyword">for</span> line <span class="hljs-keyword">in</span> filedata.split(<span class="hljs-string">"\n"</span>):
            <span class="hljs-comment"># ... snip ... : here we get a key from the line</span>
            <span class="hljs-keyword">yield</span> <span class="hljs-string">f"Retrieving data for record <span class="hljs-subst">{key}</span>..."</span>
            record = getDataFromAPI(key)
            <span class="hljs-comment"># ... snip ... : update the record with data from the line</span>
            <span class="hljs-keyword">yield</span> <span class="hljs-string">f"Updating record\n"</span>
            updateDataViaAPI(record)
    <span class="hljs-keyword">return</span> Response(innerHandler(), content_type=<span class="hljs-string">'text/event-stream'</span>)
</code></pre>
<p>Notice that, inside the loop, the first <strong>yield</strong> doesn't end with a newline, while the second does. This keeps the getting and updating for a single record on the same line (probably - window width matters here) of the status updates. This is why we use a <code>&lt;pre&gt;</code> element for the status updates.</p>
<h3 id="a-viola">a Viola</h3>
<p>So there you have a pretty simple client-server streaming solution to keep a user aware of the progress of a long-running process.</p>
<p>There are other features you may need to add, such as error handling, but I leave that as an exercise for another day. </p>
<p>Happy streaming!</p>
]]></content:encoded></item></channel></rss>