Jekyll2023-11-26T01:55:07+00:00/feed.xmlDaniel CoatsIntroducing Training Planner2021-08-08T00:00:00+00:002021-08-08T00:00:00+00:00/2021/08/08/introducing-training-planner<p>Back in April, I decided to <a href="/2021/04/11/running-training-planner-the-idea/">build an app</a> to help me train for running events, now that in-person events are starting up again. About a month ago I entered Seattle’s <a href="https://lakeunion10k.com">Lake Union 10K</a>. This gave me the motivation to finally finish up the app.</p>
<p><a href="https://trainingplanner.danielcoats.nz/" target="_blank"><img src="/assets/images/training-planner/training-planner.png" alt="Screenshot of the Training Planner website" /></a></p>
<p>The goal was to make it easy to create a training plan, and track progress against the plan. Many fitness apps provide rich tracking for individual workouts, but few tools for planning or tracking a program which might span weeks or months. In my experience using it so far, Training Planner is an ideal companion to Strava, and an improvement on my previous approach of using a Word document.</p>
<p>I had to cut some of the features I originally planned. For now, your data is only stored locally in your web browser, so if you change browser or switch to a different computer, you’ll have no way of accessing your training plans. I added a Download button and the ability to import the downloaded file, so there is at least a way of backing up and restoring a snapshot of your data.</p>
<p>I may write more about the technical design in future blog posts. But for now, here are some of the technologies I used:</p>
<ul>
<li><a href="http://create-react-app.dev">Create React App</a></li>
<li><a href="https://redux-toolkit.js.org">Redux Toolkit</a></li>
<li><a href="https://www.typescriptlang.org">TypeScript</a></li>
<li><a href="http://react-bootstrap.github.io">React Bootstrap</a></li>
<li><a href="https://formik.org">Formik</a></li>
<li><a href="https://dexie.org">Dexie</a></li>
</ul>
<p>Check it out at <a href="https://trainingplanner.danielcoats.nz/" target="_blank">trainingplanner.danielcoats.nz</a>. You can find the <a href="https://github.com/danielcoats/training-planner">source code on GitHub</a>.</p>
<p>I also discussed my approach to styling the React components in <a href="/2021/05/08/styling-react-components/">a previous blog post</a>. The app is deployed via GitHub Actions to Azure Static Web Apps.</p>
<p>If you have any feedback, <a href="/contact">contact me</a> or <a href="https://github.com/danielcoats/training-planner/issues/new">open an issue</a> on GitHub.</p>Back in April, I decided to build an app to help me train for running events, now that in-person events are starting up again. About a month ago I entered Seattle’s Lake Union 10K. This gave me the motivation to finally finish up the app.Building a Serverless Contact Form2021-07-18T00:00:00+00:002021-07-18T00:00:00+00:00/2021/07/18/serverless-contact-form<p>A few months back, I finally decided to migrate my personal website from a self-hosted WordPress installation running on a Linode server, to a simple static website built with <a href="https://jekyllrb.com/">Jekyll</a> and served by <a href="https://docs.github.com/en/pages/setting-up-a-github-pages-site-with-jekyll">GitHub Pages</a>.</p>
<p>One of the challenges of switching to a static site was the contact form. Unlike my previous setup, this site is served by GitHub, and I don’t own the backend, so I couldn’t write code to process and send the email server-side. I also didn’t want to stand up a separate web server just for that.</p>
<p>Initially, I just gave my email address instead. But honestly, I can’t stand <code class="language-plaintext highlighter-rouge">mailto:</code> links, and much prefer filling out a form right there on the site. As a developer, I like a good challenge.</p>
<h4 id="going-serverless">Going Serverless</h4>
<p>These were my requirements: the design should match my site and be usable on large and small screens, and the form should provide basic spam protection and reliable delivery. Form submissions should be validated and sent without a page reload.</p>
<p>Hoping for a quick win, I looked at form builders. But the options I looked at didn’t allow me to receive the submission directly by email, and offered limited scope to customize the design. Given I didn’t want the expense or hassle of spinning up an entire VM or app service for a simple contact form, I decided to go serverless.</p>
<h4 id="the-front-end">The Front-End</h4>
<p>But first, I needed a form that I could hook up to the serverless endpoint. I started by writing the markup and adding some basic styling. <a href="/contact">As you can see</a>, the form is simple, with just two fields: the sender’s email address, message, and a reCAPTCHA to hopefully block any bots (the “I’m not a robot” checkbox that you see everywhere).</p>
<p>I used CSS Grid for the form layout, which made it super easy to create a two column layout that collapses to a single column at small screen sizes:</p>
<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">.contact-form</span> <span class="p">{</span>
<span class="nl">display</span><span class="p">:</span> <span class="n">grid</span><span class="p">;</span>
<span class="py">grid-template-columns</span><span class="p">:</span> <span class="m">150px</span> <span class="m">1</span><span class="n">fr</span><span class="p">;</span>
<span class="py">gap</span><span class="p">:</span> <span class="m">16px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.contact-form</span> <span class="nt">label</span> <span class="p">{</span>
<span class="nl">grid-column</span><span class="p">:</span> <span class="m">1</span> <span class="p">/</span> <span class="m">2</span><span class="p">;</span>
<span class="nl">line-height</span><span class="p">:</span> <span class="m">40px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.contact-form</span> <span class="nc">.form-control</span> <span class="p">{</span>
<span class="nl">grid-column</span><span class="p">:</span> <span class="m">2</span> <span class="p">/</span> <span class="m">3</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">@media</span> <span class="n">screen</span> <span class="n">and</span> <span class="p">(</span><span class="n">max-width</span><span class="p">:</span> <span class="m">600px</span><span class="p">)</span> <span class="p">{</span>
<span class="nc">.contact-form</span> <span class="p">{</span>
<span class="py">grid-template-columns</span><span class="p">:</span> <span class="m">1</span><span class="n">fr</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.contact-form</span> <span class="nt">label</span><span class="o">,</span>
<span class="nc">.contact-form</span> <span class="nc">.form-control</span> <span class="p">{</span>
<span class="nl">grid-column</span><span class="p">:</span> <span class="nb">auto</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>With the markup done, I added the event listeners for validation and submission. I’m using the blur event to validate each input as you click or tab out of the element:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">contactForm</span><span class="p">.</span><span class="nx">elements</span><span class="p">.</span><span class="nx">emailAddress</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">blur</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">ev</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">validateEmail</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>
<span class="p">});</span>
<span class="kd">function</span> <span class="nx">validateEmail</span><span class="p">(</span><span class="nx">el</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">el</span><span class="p">.</span><span class="nx">value</span><span class="p">.</span><span class="nx">length</span> <span class="o">></span> <span class="mi">0</span> <span class="o">&&</span> <span class="nx">isValidEmail</span><span class="p">(</span><span class="nx">el</span><span class="p">.</span><span class="nx">value</span><span class="p">))</span> <span class="p">{</span>
<span class="nx">hideElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">emailError</span><span class="dl">'</span><span class="p">);</span>
<span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">showElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">emailError</span><span class="dl">'</span><span class="p">);</span>
<span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>A neat trick I learned: you can access all of the forms on a site using <code class="language-plaintext highlighter-rouge">document.forms</code> and forms are keyed on the name of the form: <code class="language-plaintext highlighter-rouge">const contactForm = document.forms.contact;</code></p>
<p>When the user submits the form, the inputs are validated again, including the reCAPTCHA field. The fields are then serialized and sent as JSON to the contact form endpoint. To make it easy to test locally, I conditionally set the endpoint URL based on whether the site is in development mode or live. In the Jekyll template code:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><script></span>
<span class="kd">const</span> <span class="nx">CONTACT_BASE_URL</span> <span class="o">=</span> <span class="p">{</span><span class="o">%</span> <span class="k">if</span> <span class="nx">jekyll</span><span class="p">.</span><span class="nx">environment</span> <span class="o">==</span> <span class="dl">"</span><span class="s2">development</span><span class="dl">"</span> <span class="o">%</span><span class="p">}</span><span class="dl">"</span><span class="s2">http://localhost:7071</span><span class="dl">"</span><span class="p">{</span><span class="o">%</span> <span class="k">else</span> <span class="o">%</span><span class="p">}</span><span class="dl">"</span><span class="s2">https://contactform4891.azurewebsites.net</span><span class="dl">"</span><span class="p">{</span><span class="o">%</span> <span class="nx">endif</span> <span class="o">%</span><span class="p">};</span>
<span class="nt"></script></span>
</code></pre></div></div>
<h4 id="building-the-function">Building the Function</h4>
<p>There are loads of services out there for building “serverless” apps, but for this project I went with Azure Functions and followed the docs for <a href="https://docs.microsoft.com/en-us/azure/azure-functions/create-first-function-vs-code-csharp">building a C# function using Visual Studio Code</a>. VS Code provides support for deploying, debugging, and configuring the app within the editor, but you could just as well develop from the command line.</p>
<p>Once finished, I had a function app that I could debug locally and deploy to the public web. Next up was the business logic. The function needed to do four things: receive and parse the data, validate that the reCAPTCHA token is legit, build the email, and pass it off to SendGrid to deliver.</p>
<h4 id="integrating-sendgrid">Integrating SendGrid</h4>
<p>I chose SendGrid for the email delivery because they offer a free plan, capped at a generous 100 emails/day, which is ideal for a personal website like this. SendGrid also has <a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-sendgrid?tabs=csharp">pre-built bindings for Azure Functions</a> which makes it dead simple to integrate.</p>
<p>I installed the SendGrid binding as a <a href="https://www.nuget.org/packages/Microsoft.Azure.WebJobs.Extensions.SendGrid/">nuget package</a> and added the SendGrid attribute to the method signature, as per <a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-sendgrid?tabs=csharp#asynchronous">the example here</a>. I added the logic for reading the request body and tested it by running the Jeykll site and Azure Function locally.</p>
<p>Next up, I added reCAPTCHA validation. When the user completes the reCAPTCHA on the site, a token is generated to confirm they’ve passed the challenge, and I send this token over an HTTPS connection to the serverless endpoint along with the user input when the form is submitted.</p>
<p>But the server has no way of knowing that this token is legit, so in the serverless function itself, I send the token along with a private key to the reCAPTCHA verification endpoint: <code class="language-plaintext highlighter-rouge">https://www.google.com/recaptcha/api/siteverify?secret={secret}&response={token}</code></p>
<p>As per best practice, I have a separate reCAPTCHA secret key for local development and production, and each key is only valid when sent from its respective domain. Along with the SendGrid keys, these are stored in configuration files that are never checked in, and referenced in code via <code class="language-plaintext highlighter-rouge">Environment.GetEnvironmentVariable()</code>.</p>
<p>If reCAPTCHA responds with a success message, then I build the email and pass it off to the SendGrid message collector to send. If everything has succeeded to this point, the function responds with a 200 OK and the contact form tells the user the submission succeeded. A few moments later, the message arrives in my inbox:</p>
<p><img src="/assets/images/contact-form/email.png" alt="Submission received by email" /></p>
<p>You can find the source code for the serverless function <a href="https://github.com/danielcoats/danielcoats.github.io/blob/main/functions/SubmitContactForm.cs">here</a> and the JavaScript code <a href="https://github.com/danielcoats/danielcoats.github.io/blob/main/assets/contact.js">here</a>.</p>A few months back, I finally decided to migrate my personal website from a self-hosted WordPress installation running on a Linode server, to a simple static website built with Jekyll and served by GitHub Pages.Styling React Components2021-05-08T00:00:00+00:002021-05-08T00:00:00+00:00/2021/05/08/styling-react-components<p>SASS, CSS Modules, custom properties, utility classes… all of these tools solve some problem with CSS, and each has its advocates. But when you have a React component in need of some style, which tool should you reach for? Even for <a href="/2021/04/11/running-training-planner-the-idea/">a small project</a>, you don’t want to experience decision fatigue every time you need to write some CSS. And ideally, you want a tool, or set of tools, that encourage consistency, especially with regards to colors, sizing, and other common styling concerns.</p>
<p>Here’s the solution I ended up with. It is by no means perfect, but hopefully it achieves the objectives above.</p>
<h2 id="importing-bootstrap-css">Importing Bootstrap CSS</h2>
<p>I’m using Bootstrap, so at a minimum, I need to include the bootstrap styles. Since I’m using <code class="language-plaintext highlighter-rouge">create-react-app</code> which includes <code class="language-plaintext highlighter-rouge">sass-loader</code> by default, I followed the <a href="https://getbootstrap.com/docs/5.0/customize/optimize/">Bootstrap optimization</a> advice, and I commented out the styles for components that I’m not using yet, such as carousels and modals, in a file called <code class="language-plaintext highlighter-rouge">bootstrap.scss</code>. I then imported this in <code class="language-plaintext highlighter-rouge">global.scss</code> which is imported in the top-level <code class="language-plaintext highlighter-rouge">index.js</code> file so that it is available globally (more on that later).</p>
<p>A snippet of <code class="language-plaintext highlighter-rouge">bootstrap.scss</code>:</p>
<div class="language-scss highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// --------------------------------------------------</span>
<span class="c1">// Import Bootstrap</span>
<span class="c1">// Unused files commented to reduce the amount of CSS</span>
<span class="c1">// --------------------------------------------------</span>
<span class="c1">// Configuration</span>
<span class="k">@import</span> <span class="s1">'~bootstrap/scss/functions'</span><span class="p">;</span>
<span class="k">@import</span> <span class="s1">'~bootstrap/scss/variables'</span><span class="p">;</span>
<span class="k">@import</span> <span class="s1">'~bootstrap/scss/mixins'</span><span class="p">;</span>
<span class="c1">// Layout & components</span>
<span class="k">@import</span> <span class="s1">'~bootstrap/scss/root'</span><span class="p">;</span>
<span class="k">@import</span> <span class="s1">'~bootstrap/scss/reboot'</span><span class="p">;</span>
<span class="c1">// @import '~bootstrap/scss/toasts';</span>
<span class="c1">// @import '~bootstrap/scss/modal';</span>
<span class="nc">...</span>
</code></pre></div></div>
<h2 id="adding-component-styles">Adding component styles</h2>
<p>You might think we’re done with CSS now. While the Bootstrap components are a great start, we still need somewhere to write component-specific styles. Bootstrap provides some built-in utility classes for padding, margins, sizing, etc. but these only cover a subset of CSS properties, so at some point it is necessary to actually write some CSS.</p>
<p>React encourages writing CSS at a component level to keep things modular and have the CSS close to where it is being used. But if all CSS styles are scoped to a single component, it is harder to keep CSS consistent between components. Ideally, I want to use the variables already defined in Bootstrap - but I don’t want to have to <code class="language-plaintext highlighter-rouge">@import '~/bootstrap/_variables.scss'</code> in every component, which creates duplication, and I may want to override some of these variables.</p>
<p>What about CSS custom properties? These are native to the browser, so presumably have good performance. They can be easily tweaked in dev tools for rapid iteration. And they can be defined once at the top-level and used everywhere - no imports required. But, as of v4.6.x, Bootstrap only defines <a href="https://getbootstrap.com/docs/4.6/getting-started/theming/#css-variables">a tiny subset</a> of its SASS variables as custom properties. It sounds like there are <a href="https://github.com/twbs/bootstrap/issues/26596#issuecomment-742799995">plans to improve coverage</a> in future, but this didn’t make the cut for v5, and my project is using v4.6.</p>
<h2 id="bringing-it-all-together">Bringing it all together</h2>
<p>So this is where I am: I want to write some custom (S)CSS at the component level, but as much as possible, I want to re-use the styles already defined in Bootstrap, so that (a) there is consistency between components and (b) I can make changes to common styles in one place, and have it reflected anywhere that style is used. My solution extends upon Bootstrap’s approach.</p>
<p>I created a file called <code class="language-plaintext highlighter-rouge">global.scss</code> and imported it in the root level <code class="language-plaintext highlighter-rouge">index.js</code>. This file does a few things:</p>
<ul>
<li>Imports custom SASS variables from <code class="language-plaintext highlighter-rouge">theme.scss</code> that override some of the Bootstrap variables</li>
<li>Imports all of the required Bootstrap styles</li>
<li>Exports any commonly used variables as custom properties defined on <code class="language-plaintext highlighter-rouge">:root</code></li>
</ul>
<p>Whenever I find myself reaching for a color, size, or other common property, I add it in here first, so I can use it in any components that might need that property. This way, I get the power of SASS, with the usability of custom properties. Here is <code class="language-plaintext highlighter-rouge">global.scss</code>:</p>
<div class="language-scss highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Override default variables before importing Bootstrap</span>
<span class="k">@import</span> <span class="s1">'theme.scss'</span><span class="p">;</span>
<span class="c1">// Import Bootstrap</span>
<span class="k">@import</span> <span class="s1">'bootstrap.scss'</span><span class="p">;</span>
<span class="c1">// Define custom properties</span>
<span class="nd">:root</span> <span class="p">{</span>
<span class="k">@each</span> <span class="nv">$shade</span><span class="o">,</span> <span class="nv">$value</span> <span class="n">in</span> <span class="nv">$grays</span> <span class="p">{</span>
<span class="nt">--gray-</span><span class="si">#{</span><span class="nv">$shade</span><span class="si">}</span><span class="nd">:</span> <span class="si">#{</span><span class="nv">$value</span><span class="si">}</span><span class="p">;</span>
<span class="p">}</span>
<span class="na">--border-radius</span><span class="p">:</span> <span class="si">#{</span><span class="nv">$border-radius</span><span class="si">}</span><span class="p">;</span>
<span class="k">@each</span> <span class="nv">$size</span><span class="o">,</span> <span class="nv">$length</span> <span class="n">in</span> <span class="nv">$spacers</span> <span class="p">{</span>
<span class="nt">--spacing-</span><span class="si">#{</span><span class="nv">$size</span><span class="si">}</span><span class="nd">:</span> <span class="si">#{</span><span class="nv">$length</span><span class="si">}</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Each component has its own CSS file with the <code class="language-plaintext highlighter-rouge">\*.module</code> extension to indicate that it is a <a href="https://create-react-app.dev/docs/adding-a-css-modules-stylesheet/">CSS Module</a> and should be locally-scoped. For example, if I were writing a Button component, I would import <code class="language-plaintext highlighter-rouge">Button.module.scss</code> into my <code class="language-plaintext highlighter-rouge">Button.js</code> component like so:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nx">styles</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./button.module.scss</span><span class="dl">'</span><span class="p">;</span><span class="s2">`
</span></code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">button.module.scss</code>:</p>
<div class="language-scss highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">.default</span> <span class="p">{</span>
<span class="nl">background-color</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">gray-200</span><span class="p">);</span>
<span class="nl">color</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">gray-600</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>I can reference this in my component using <code class="language-plaintext highlighter-rouge">className={styles.default}</code>. At compile time, the CSS above is transformed into the following, with a prefix and hash to prevent class name conflicts:</p>
<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">.button_default__KKOfe</span> <span class="p">{</span>
<span class="nl">background-color</span><span class="p">:</span> <span class="n">var</span><span class="p">(</span><span class="n">--light</span><span class="p">);</span>
<span class="nl">border-radius</span><span class="p">:</span> <span class="n">var</span><span class="p">(</span><span class="n">--border-radius</span><span class="p">);</span>
<span class="nl">font-size</span><span class="p">:</span> <span class="nb">smaller</span><span class="p">;</span>
<span class="nl">margin-top</span><span class="p">:</span> <span class="nb">auto</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="what-about-tailwind-css">What about Tailwind CSS?</h2>
<p>A few hours in, I briefly considered throwing it all out and starting over with <a href="https://tailwindcss.com/">Tailwind</a>, a CSS framework in which all styles are defined using utility classes. It seemed attractive for rapid iteration, as the styles are located with the component markup, and there is no need to think about the cascading nature of CSS.</p>
<p>But there were two major factors which put me off. First, the obvious one, is that it can lead to a lot of duplication, making it harder to refactor styles down the road. And second, Tailwind doesn’t provide any components like Bootstrap. And I didn’t feel compelled to implement my own dropdown from scratch. <a href="https://tailwindui.com/">Tailwind UI</a> appears to solve this problem, but it is costly for a small side project like this.</p>
<p>For this project, Bootstrap was the most pragmatic path, but I hope to revisit Tailwind in a future project, or maybe even <a href="https://www.scottbrady91.com/General/Adding-Tailwind-Utility-Classes-to-your-Bootstrap-Website">use them together</a>.</p>
<p>As with front-end web development in general, the CSS landscape is always changing, with new technologies bubbling up, only to be replaced by something entirely new. But for now, I have found an approach that works, and I can get back to building.</p>SASS, CSS Modules, custom properties, utility classes… all of these tools solve some problem with CSS, and each has its advocates. But when you have a React component in need of some style, which tool should you reach for? Even for a small project, you don’t want to experience decision fatigue every time you need to write some CSS. And ideally, you want a tool, or set of tools, that encourage consistency, especially with regards to colors, sizing, and other common styling concerns.Prototyping the User Interface2021-04-16T00:00:00+00:002021-04-16T00:00:00+00:00/2021/04/16/prototyping-user-interface<p>In my previous post, I described <a href="/2021/04/11/running-training-planner-the-idea/">how I train for a running race</a> and how I’m hoping to replicate and streamline that process with an app. But before I get waist deep in JavaScript, I want to step back and sketch out the design, so that I have something to refer back to as I build out the app. In this post, I describe my process for turning an idea into a low-fi prototype.</p>
<p>I set myself a few loose requirements for this prototype. I wanted to capture the three main screens of the app: the empty state, the training log, and the individual workout edit screen. I decided to design for a desktop web browser first, and revisit mobile later on.</p>
<h2 id="my-process">My Process</h2>
<p>When I start working on a project that involves some sort of user interface, I usually have a rough mental image of how it should look. I use that as a starting point, and pull in other ideas as needed. I typically design from the outside in, starting with the large visual elements before moving to the more specific details. Once I have the bones of the design, the remainder of the process is all about iterating until I have something that I could feasibly turn into a functional prototype.</p>
<p>Take the training calendar below as an example. I started with a seven column table for the days of the week. I started filling the table with a week of training, but realized that some days I do multiple workouts—Fridays I will typically do a jog in the morning and a strength or stability workout in the evening. So I switched to separate cards for each workout within the day column. There is nothing more satisfying than checking off something when you’re done, so I added the circular checkboxes to each card.</p>
<p>A few loose rules I follow when prototyping:</p>
<ul>
<li>Use accurate content where possible to see how it works within the design.</li>
<li>Only use grayscale colors.</li>
<li>Do not use photographs or images. Use icons sparingly.</li>
<li>Do not be tempted to add UI elements or features just because they are common. Less is more.</li>
<li>Focus on the parts that are unique or important to the design. Boilerplate can come later.</li>
</ul>
<p>These rules basically boil down to one principle: Don’t get caught up in the visual details.</p>
<h2 id="tools">Tools</h2>
<p>There is no universally best tool for prototyping. Whatever allows you to get your ideas down quickly, and in a persistent form, is the best tool. But here are some of the tools I reached for in this project.</p>
<h3 id="keynote">Keynote</h3>
<p>If I get a stroke of inspiration, pencil and paper is often the fastest way to capture the idea. But this project was simple enough that I started at my computer. I’ve tried several other tools, and each time I find myself coming back to Keynote. The features that make Keynote ideal for the task are the alignment guides, which make it easy to lay things out in a consistent way, and the fluidity of the animations and interactions i.e. zooming, panning, and resizing.</p>
<h3 id="font-awesome">Font Awesome</h3>
<p>I mentioned above that, in general, I use icons sparingly, but there are certain UI elements that need an icon, either to indicate their functionality or for emphasis. For this prototype, I used the free Font Awesome icon font. Check out <a href="https://fontawesome.com/how-to-use/on-the-desktop/setup/getting-started">Getting Started on the Desktop</a> for installation instructions. If you’re using Keynote, you’ll need to save, quit and reopen the app to load the font the first time.</p>
<p>To use the icons, either search on the Font Awesome website, or visit <a href="https://fontawesome.com/cheatsheet">their cheatsheet</a> where you can click on any of the icons to highlight it, and then copy and paste directly into Keynote. Easy as that! Since the icons are characters in a font rather than raster images, you can change the color by highlighting and changing the text color.</p>
<h2 id="prototype-designs">Prototype Designs</h2>
<h3 id="empty-state">Empty State</h3>
<p>This is the first screen that a new user will see. It is so tempting to go overboard with this screen, but if Google has taught us anything, it is that sometimes the best design is just a text box.</p>
<p><img src="/assets/images/training-planner/wireframe/empty-state.jpg" alt="Empty state screen" /></p>
<h3 id="training-log">Training Log</h3>
<p>For completeness, I filled in four weeks of training. One of the decisions I made here was to emphasize the current week, and only show a snippet of information for subsequent weeks. This and other decisions will be much easier to validate when I move to a working prototype.</p>
<p><img src="/assets/images/training-planner/wireframe/training-log.jpg" alt="Training log screen" /></p>
<h3 id="edit-workout">Edit Workout</h3>
<p>Data input is such a key component of most apps, that I probably spent the most time thinking about this screen. The main question I had (still have) is how much or how little information I need for each training. After all, I could have made it a simple text box. But maybe having the “Type” field as a dropdown or autocomplete, with a separate box for the specific workout, will make inputting data faster.</p>
<p><img src="/assets/images/training-planner/wireframe/edit-workout.jpg" alt="Edit workout screen" /></p>
<p>It is far from complete, but at least I now have something that I can begin to implement without having to think too hard about the design. Some ideas are easier to validate in a working prototype, so the design will no doubt evolve. To that end, the next step is to write some code.</p>In my previous post, I described how I train for a running race and how I’m hoping to replicate and streamline that process with an app. But before I get waist deep in JavaScript, I want to step back and sketch out the design, so that I have something to refer back to as I build out the app. In this post, I describe my process for turning an idea into a low-fi prototype.Running Training Planner: The Idea2021-04-11T00:00:00+00:002021-04-11T00:00:00+00:00/2021/04/11/running-training-planner-the-idea<p>I love my job, but sometimes I want to build something just for the fun of it. Whether to learn a new skill or technology, enhance my resume, or even build a business, side projects have many potential benefits. I’ve worked on a few, but this time I’d like to try something different: build it in the open, and journal each step. Hopefully I’ll learn a thing or two along the way. The idea for the app is simple—but before I get to that, a quick detour into running.</p>
<h2 id="how-i-train-for-running-races">How I train for running races</h2>
<p>With the COVID situation improving in my part of the world, I’m beginning to think about training for my next running race.</p>
<p>Usually I’d use a Word doc or spreadsheet to sketch out my training plan, and open it every couple of days to see what’s coming up. Like many runners, I use Strava to record my trainings. After a run, I’ll typically take a quick look at my average pace and splits for the run, and see how it stacks up against similar workouts I’ve done in the past.</p>
<p>At the start of each week, I’ll open the training doc, write a few comments on how I felt the previous week—a practice I picked up when I had a coach in high school—and then take a look at what’s in store for the coming week.</p>
<h2 id="the-idea">The idea</h2>
<p>The idea, as you might have inferred, is an app for storing and viewing my training program, and connecting with Strava to see my actual activities next to my planned training.</p>
<p>Unsurprisingly, there are already a few apps out there that offer similar functionality. I took a quick browse of the Strava <a href="https://www.strava.com/apps/training">Training apps</a> page and the closest I found was an app called <a href="https://www.finalsurge.com">FinalSurge</a>, which looks very slick. After I’m done with this project, there’s a good chance I’ll start using it. But for now, I’m looking to build something of my own.</p>
<h2 id="whats-next">What’s next?</h2>
<p>I’ll start by sketching out the design and prototyping the UI. Once I have the front-end working, I’ll move to the server side. All going well, the end result will be a series of posts like this, and a repo on GitHub.</p>I love my job, but sometimes I want to build something just for the fun of it. Whether to learn a new skill or technology, enhance my resume, or even build a business, side projects have many potential benefits. I’ve worked on a few, but this time I’d like to try something different: build it in the open, and journal each step. Hopefully I’ll learn a thing or two along the way. The idea for the app is simple—but before I get to that, a quick detour into running.