{"id":68518,"date":"2019-07-26T04:19:18","date_gmt":"2019-07-26T11:19:18","guid":{"rendered":"http:\/\/www.taterboy.com\/blog\/2019\/07\/designing-and-building-a-progressive-web-application-without-a-framework-part-2\/"},"modified":"2019-07-26T04:19:18","modified_gmt":"2019-07-26T11:19:18","slug":"designing-and-building-a-progressive-web-application-without-a-framework-part-2","status":"publish","type":"post","link":"https:\/\/www.taterboy.com\/blog\/2019\/07\/designing-and-building-a-progressive-web-application-without-a-framework-part-2\/","title":{"rendered":"Designing And Building A Progressive Web Application Without A Framework (Part 2)"},"content":{"rendered":"<link rel=\"canonical\" href=\"https:\/\/www.smashingmagazine.com\/2019\/07\/progressive-web-application-pwa-framework-part-2\/\"><title>Designing And Building A Progressive Web Application Without A Framework (Part 2)<\/title><\/p>\n<article>\n<header>\n<h1>Designing And Building A Progressive Web Application Without A Framework (Part 2)<\/h1>\n<address>Ben Frain<\/address>\n<p>                  <time datetime=\"2019-07-25T14:00:59+02:00\">2019-07-25T14:00:59+02:00<\/time><time datetime=\"2019-07-26T11:06:48+00:00\">2019-07-26T11:06:48+00:00<\/time><\/header>\n<p>The raison d\u2019\u00eatre of this adventure was to push your humble author a little in the disciplines of visual design and JavaScript coding. The functionality of the application I\u2019d decided to build was not dissimilar to a \u2018to do\u2019 application. It is important to stress that this wasn\u2019t an exercise in original thinking. The destination was far less important than the journey.<\/p>\n<p>Want to find out how the application ended up? Point your phone browser at <a href=\"https:\/\/io.benfrain.com\/\">https:\/\/io.benfrain.com<\/a>.<\/p>\n<p>Read Part One of <a href=\"https:\/\/www.smashingmagazine.com\/2019\/07\/progressive-web-application-pwa-framework-part-1\/\">Designing And Building A Progessive Web Application Without A Framework<\/a>.<\/p>\n<p>Here is a summary of what we will cover in this article:<\/p>\n<ul>\n<li>The project set-up and why I opted for Gulp as a build tool;<\/li>\n<li>Application design patterns and what they mean in practice;<\/li>\n<li>How to store and visualize application state;<\/li>\n<li>how CSS was scoped to components;<\/li>\n<li>what UI\/UX niceties were employed to make the things more \u2018app-like\u2019;<\/li>\n<li>How the remit changed through iteration.<\/li>\n<\/ul>\n<p>Let\u2019s start with the build tools.<\/p>\n<h4>Build Tools<\/h4>\n<p>In order to get my basic tooling of TypeScipt and PostCSS up and running and create a decent development experience, I would need a build system.<\/p>\n<p>In my day job, for the last five years or so, I have been building interface prototypes in HTML\/CSS and to a lesser extent, JavaScript. Until recently, I have used Gulp with any number of plugins almost exclusively to achieve my fairly humble build needs.<\/p>\n<div data-component=\"FeaturePanel\" data-audience=\"non-subscriber\" data-remove=\"true\"><\/div>\n<p>Typically I need to process CSS, convert JavaScript or TypeScript to more widely supported JavaScript, and occasionally, carry out related tasks like minifying code output and optimizing assets. Using Gulp has always allowed me to solve those issues with aplomb.<\/p>\n<p>For those unfamiliar, Gulp lets you write JavaScript to do \u2018something\u2019 to files on your local file system.<br \/>\nTo use Gulp, you typically have a single file (called <code>gulpfile.js<\/code>) in the root of your project. This JavaScript file allows you to define tasks as functions. You can add third-party \u2018Plugins\u2019, which are essentially further JavaScript functions, that deal with specific tasks.<\/p>\n<h5>An Example Gulp Task<\/h5>\n<p>An example Gulp task might be using a plugin to harness PostCSS to process to CSS when you change an authoring style sheet (<a href=\"https:\/\/github.com\/postcss\/gulp-postcss\">gulp-postcss<\/a>). Or compiling TypeScript files to vanilla JavaScript (<a href=\"https:\/\/github.com\/ivogabe\/gulp-typescript\">gulp-typescript<\/a>) as you save them. Here is a simple example of how you write a task in Gulp. This task uses the \u2018del\u2019 gulp plugin to delete all the files in a folder called \u2018build\u2019:<\/p>\n<pre><code>var del = require(\"del\");\n\ngulp.task(\"clean\", function() {\n  return del([\"build\/**\/*\"]);\n});\n<\/code><\/pre>\n<p>The <code>require<\/code> assigns the <code>del<\/code> plugin to a variable. Then the <code>gulp.task<\/code> method is called. We name the task with a string as the first argument (\u201cclean\u201d) and then run a function, which in this case uses the \u2018del\u2019 method to delete the folder passed to it as an argument. The asterisk symbols there are \u2018glob\u2019 patterns which essentially say \u2018any file in any folder\u2019 of the build folder.<\/p>\n<p>Gulp tasks can get heaps more complicated but in essence, that is the mechanics of how things are handled. The truth is, with Gulp, you don\u2019t need to be a JavaScript wizard to get by; grade 3 copy and paste skills are all you need.<\/p>\n<p>I\u2019d stuck with Gulp as my default build tool\/task runner for all these years with a policy of \u2018if it ain\u2019t broke; don\u2019t try and fix it\u2019.<\/p>\n<p>However, I was worried I was getting stuck in my ways. It\u2019s an easy trap to fall into. First, you start holidaying the same place every year, then refusing to adopt any new fashion trends before eventually and steadfastly refusing to try out any new build tools.<\/p>\n<p>I\u2019d heard plenty of chatter on the Internets about \u2018Webpack\u2019 and thought it was my duty to try a project using the new-fangled toast of the front-end developer cool-kids.<\/p>\n<h5>Webpack<\/h5>\n<p>I distinctly remember skipping over to the <a href=\"https:\/\/webpack.js.org\/\">webpack.js.org<\/a> site with keen interest. The first explanation of what Webpack is and does started like this:<\/p>\n<pre><code>import bar from '.\/bar';\n<\/code><\/pre>\n<p>Say what? In the words of Dr. Evil, \u201cThrow me a frickin\u2019 bone here, Scott\u201d.<\/p>\n<p>I know it\u2019s my own hang-up to deal with but I\u2019ve developed a revulsion to any coding explanations that mention \u2018foo\u2019, \u2018bar\u2019 or \u2018baz\u2019. That plus the complete lack of succinctly describing what Webpack was actually <em>for<\/em> had me suspecting it perhaps wasn\u2019t for me.<\/p>\n<p>Digging a little further into the Webpack documentation, a slightly less opaque explanation was offered, \u201cAt its core, webpack is a static module bundler for modern JavaScript applications\u201d.<\/p>\n<p>Hmmm. Static module bundler. Was that what I wanted? I wasn\u2019t convinced. I read on but the more I read, the less clear I was. Back then, concepts like dependency graphs, hot module reloading, and entry points were essentially lost on me.<\/p>\n<p>A couple of evenings of researching Webpack later, I abandoned any notion of using it.<\/p>\n<p>I\u2019m sure in the right situation and more experienced hands, Webpack is immensely powerful and appropriate but it seemed like complete overkill for my humble needs. Module bundling, tree-shaking, and hot-module reloading sounded great; I just wasn\u2019t convinced I needed them for my little \u2018app\u2019.<\/p>\n<p>So, back to Gulp then.<\/p>\n<p>On the theme of not changing things for change sake, another piece of technology I wanted to evaluate was Yarn over NPM for managing project dependencies. Until that point, I had always used NPM and Yarn was getting touted as a better, faster alternative. I don\u2019t have much to say about Yarn other than if you are currently using NPM and everything is OK, you don\u2019t need to bother trying Yarn.<\/p>\n<p>One tool that arrived too late for me to appraise for this application is Parceljs. With zero configuration and a BrowserSync like browser reloading backed in, I\u2019ve since found great utility in it! In addition, in Webpack\u2019s defense, I&#8217;m told that v4 onwards of Webpack doesn\u2019t require a configuration file. Anecdotally, in a more <a href=\"https:\/\/twitter.com\/benfrain\/status\/1103328673925611521\">recent poll I ran on Twitter<\/a>, of the 87 respondents, over half chose Webpack over Gulp, Parcel or Grunt.<\/p>\n<p>I started my Gulp file with basic functionality to get up and running.<\/p>\n<p>A \u2018default\u2019 task would watch the \u2018source\u2019 folders of style sheets and TypeScript files and compile them out to a <code>build<\/code> folder along with the basic HTML and associated source maps.<\/p>\n<p>I got <a href=\"https:\/\/www.browsersync.io\/\">BrowserSync<\/a> working with Gulp too. I might not know what to do with a Webpack configuration file but that didn\u2019t mean I was some kind of animal. Having to manually refresh the browser while iterating with HTML\/CSS is <em>soooo<\/em> 2010 and BrowserSync gives you that short feedback and iteration loop that is so useful for front-end coding.<\/p>\n<p>Here is the <a href=\"https:\/\/github.com\/benfrain\/whosin\/commit\/f271aa54a576867225d2403f5e95e4bcc2483c9e#diff-b9e12334e9eafd8341a6107dd98510c9\">basic gulp file as of 11.6.2017<\/a><\/p>\n<p>You can see how I tweaked the Gulpfile nearer to the end of shipping, <a href=\"https:\/\/github.com\/benfrain\/whosin\/commit\/9994cae5281e096b356d54b4aebdeb3ffd0b794c#diff-b9e12334e9eafd8341a6107dd98510c9\">adding minification with ugilify<\/a>:<\/p>\n<h4>Project Structure<\/h4>\n<p>By consequence of my technology choices, some elements of code organization for the application were defining themselves. A <code>gulpfile.js<\/code> in the root of the project, a <code>node_modules<\/code> folder (where Gulp stores plugin code) a <code>preCSS<\/code> folder for the authoring style sheets, a <code>ts<\/code> folder for the TypeScript files, and a <code>build<\/code> folder for the compiled code to live.<\/p>\n<p>The idea was to have an <code>index.html<\/code> that contained the \u2018shell\u2019 of the application, including any non-dynamic HTML structure and then links to the styles and the JavaScript file that would make the application work. On disk, it would look something like this:<\/p>\n<pre><code>build\/\nnode_modules\/\npreCSS\/\n    img\/\n    partials\/\n    styles.css\nts\/\n.gitignore\ngulpfile.js\nindex.html\npackage.json\ntsconfig.json\n<\/code><\/pre>\n<p>Configuring BrowserSync to look at that <code>build<\/code> folder meant I could point my browser at <code>localhost:3000<\/code> and all was good.<\/p>\n<p>With a basic build system in place, files organization settled and some basic designs to make a start with, I had run-out of procrastination fodder I could legitimately use to prevent me from actually building the thing!<\/p>\n<div><\/div>\n<h3>Writing An Application<\/h3>\n<p>The principle of how the application would work was this. There would be a store of data. When the JavaScript loaded it would load that data, loop through each player in the data, creating the HTML needed to represent each player as a row in the layout and placing them in the appropriate in\/out section. Then interactions from the user would move a player from one state to another. Simple.<\/p>\n<p>When it came to actually writing the application, the two big conceptual challenges that needed to be understood were:<\/p>\n<ol>\n<li>How to represent the data for an application in a manner that could be easily extended and manipulated;<\/li>\n<li>How to make the UI react when data was changed from user input.<\/li>\n<\/ol>\n<p>One of the simplest ways to represent a data structure in JavaScript is with object notation. That sentence reads a little computer science-y. More simply, an \u2018object\u2019 in JavaScript lingo is a handy way of storing data.<\/p>\n<p>Consider this JavaScript object assigned to a variable called <code>ioState<\/code> (for In\/Out State):<\/p>\n<div>\n<pre><code>var ioState = {\n    Count: 0, \/\/ Running total of how many players\n    RosterCount: 0; \/\/ Total number of possible players\n    ToolsExposed: false, \/\/ Whether the UI for the tools is showing\n    Players: [], \/\/ A holder for the players\n}\n<\/code><\/pre>\n<\/div>\n<p>If you don\u2019t really know JavaScript that well, you can probably at least grasp what\u2019s going on: each line inside the curly braces is a property (or \u2018key\u2019 in JavaScript parlance) and value pair. You can set all sorts of things to a JavaScript key. For example, functions, arrays of other data or nested objects. Here\u2019s an example:<\/p>\n<pre><code>var testObject = {\n  testFunction: function() {\n    return \"sausages\";\n  },\n  testArray: [3,7,9],\n  nestedtObject {\n    key1: \"value1\",\n    key2: 2,\n  }\n}\n<\/code><\/pre>\n<p>The net result is that using that kind of data structure you can get, and set, any of the keys of the object. For example, if we want to set the count of the ioState object to 7:<\/p>\n<pre><code>ioState.Count = 7;\n<\/code><\/pre>\n<p>If we want to set a piece of text to that value, the notation works like this:<\/p>\n<pre><code>aTextNode.textContent = ioState.Count;\n<\/code><\/pre>\n<p>You can see that getting values and setting values to that state object is simple in the JavaScript side of things. However, reflecting those changes in the User Interface is less so. This is the main area where frameworks and libraries seek to abstract away the pain.<\/p>\n<p>In general terms, when it comes to dealing with updating the user interface based upon state, it\u2019s preferable to avoid querying the DOM, as this is generally considered a sub-optimal approach.<\/p>\n<p>Consider the In\/Out interface. It\u2019s typically showing a list of potential players for a game. They are vertically listed, one under the other, down the page.<\/p>\n<p>Perhaps each player is represented in the DOM with a <code>label<\/code> wrapping a checkbox <code>input<\/code>. This way, clicking a player would toggle the player to \u2018In\u2019 by virtue of the label making the input \u2018checked\u2019.<\/p>\n<p>To update our interface, we might have a \u2018listener\u2019 on each input element in the JavaScript. On a click or change, the function queries the DOM and counts how many of our player inputs are checked. On the basis of that count, we would then update something else in the DOM to show the user how many players are checked.<\/p>\n<p>Let\u2019s consider the cost of that basic operation. We are listening on multiple DOM nodes for the click\/check of an input, then querying the DOM to see how many of a particular DOM type are checked, then writing something into the DOM to show the user, UI wise, the number of players we just counted.<\/p>\n<p>The alternative would be to hold the application state as a JavaScript object in memory. A button\/input click in the DOM could merely update the JavaScript object and then, based on that change in the JavaScript object, do a single-pass update of the all interface changes that are needed. We could skip querying the DOM to count the players as the JavaScript object would already hold that information.<\/p>\n<p>So. Using a JavaScript object structure for the state seemed simple but flexible enough to encapsulate the application state at any given time. The theory of how this could be managed seemed sound enough too \u2013\u00a0this must be what phrases like \u2018one-way data flow\u2019 were all about? However, the first real trick would be in creating some code that would automatically update the UI based on any changes to that data.<\/p>\n<p>The good news is that smarter people than I have already figured this stuff out (<em>thank goodness!<\/em>). People have been perfecting approaches to this kind of challenge since the dawn of applications. This category of problems is the bread and butter of \u2018design patterns\u2019. The moniker \u2018design pattern\u2019 sounded esoteric to me at first but after digging just a little it all started to sound less computer science and more common sense.<\/p>\n<h3>Design Patterns<\/h3>\n<p>A design pattern, in computer science lexicon, is a pre-defined and proven way of solving a common technical challenge. Think of design patterns as the coding equivalent of a cooking recipe.<\/p>\n<p>Perhaps the most famous literature on design patterns is &#8220;Design Patterns: Elements of Reusable Object-Oriented Software&#8221; from back in 1994. Although that deals with C++ and smalltalk the concepts are transferable. For JavaScript, Addy Osmani\u2019s &#8220;Learning JavaScript Design Patterns&#8221; covers similar ground. You can also read it online for free <a href=\"https:\/\/addyosmani.com\/resources\/essentialjsdesignpatterns\/book\/\">here<\/a>.<\/p>\n<h4>Observer Pattern<\/h4>\n<p>Typically design patterns are split into three groups: Creational, Structural and Behavioural. I was looking for something Behavioural that helped to deal with communicating changes around the different parts of the application.<\/p>\n<p>More recently, I have seen and read a really great deep-dive on implementing reactivity inside an app by Gregg Pollack. There is both a blog post and video for your enjoyment <a href=\"https:\/\/www.vuemastery.com\/courses\/advanced-components\/build-a-reactivity-system\/\">here<\/a>.<\/p>\n<p>When reading the opening description of the \u2018Observer\u2019 pattern in <code>Learning JavaScript Design Patterns<\/code> I was pretty sure it was the pattern for me. It is described thus:<\/p>\n<blockquote><p>The Observer is a design pattern where an object (known as a subject) maintains a list of objects depending on it (observers), automatically notifying them of any changes to state.<\/p>\n<p>When a subject needs to notify observers about something interesting happening, it broadcasts a notification to the observers (which can include specific data related to the topic of the notification).<\/p><\/blockquote>\n<p>The key to my excitement was that this seemed to offer some way of things updating <strong>themselves<\/strong> when needed.<\/p>\n<p>Suppose the user clicked a player named \u201cBetty\u201d to select that she was \u2018In\u2019 for the game. A few things might need to happen in the UI:<\/p>\n<ol>\n<li>Add 1 to the playing count<\/li>\n<li>Remove Betty from the \u2018Out\u2019 pool of players<\/li>\n<li>Add Betty to the \u2018In\u2019 pool of players<\/li>\n<\/ol>\n<p>The app would also need to update the data that represented the UI. What I was very keen to avoid was this:<\/p>\n<pre><code>playerName.addEventListener(\"click\", playerToggle);\n\nfunction playerToggle() {\n  if (inPlayers.includes(e.target.textContent)) {\n    setPlayerOut(e.target.textContent);\n    decrementPlayerCount();\n  } else {\n    setPlayerIn(e.target.textContent);\n    incrementPlayerCount();\n  }\n}\n<\/code><\/pre>\n<p>The aim was to have an elegant data flow that updated what was needed in the DOM when and if the central data was changed.<\/p>\n<div><\/div>\n<p>With an Observer pattern, it was possible to send out updates to the state and therefore the user interface quite succinctly. Here is an example, the actual function used to add a new player to the list:<\/p>\n<pre><code>function itemAdd(itemString: string) {\n  let currentDataSet = getCurrentDataSet();\n  var newPerson = new makePerson(itemString);\n  io.items[currentDataSet].EventData.splice(0, 0, newPerson);\n  io.notify({\n    items: io.items\n  });\n}\n<\/code><\/pre>\n<p>The part relevant to the Observer pattern there being the <code>io.notify<\/code> method. As that shows us modifying the <code>items<\/code> part of the application state, let me show you the observer that listened for changes to \u2018items\u2019:<\/p>\n<pre><code>io.addObserver({\n  props: [\"items\"],\n  callback: function renderItems() {\n    \/\/ Code that updates anything to do with items...\n  }\n});\n<\/code><\/pre>\n<p>We have a notify method that makes changes to the data and then Observers to that data that respond when properties they are interested in are updated.<\/p>\n<p>With this approach, the app could have observables watching for changes in any property of the data and run a function whenever a change occurred.<\/p>\n<p>If you are interested in the Observer pattern I opted for, I describe it more fully <a href=\"https:\/\/benfrain.com\/prototyping-reactive-interfaces-with-an-adapted-javascript-observer-pattern\/\">here<\/a>.<\/p>\n<p>There was now an approach for updating the UI effectively based on state. Peachy. However, this still left me with two glaring issues.<\/p>\n<p>One was how to store the state across page reloads\/sessions and the fact that despite the UI working, visually, it just wasn\u2019t very \u2018app like\u2019. For example, if a button was pressed the UI instantly changed on screen. It just wasn\u2019t particularly compelling.<\/p>\n<p>Let\u2019s deal with the storage side of things first.<\/p>\n<h4>Saving State<\/h4>\n<p>My primary interest from a development side entering into this centered on understanding how app interfaces could be built and made interactive with JavaScript. How to store and retrieve data from a server or tackle user-authentication and logins was \u2018out of scope\u2019.<\/p>\n<p>Therefore, instead of hooking up to a web service for the data storage needs, I opted to keep all data on the client. There are a number of web platform methods of storing data on a client. I opted for <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Window\/localStorage\"><code>localStorage<\/code><\/a>.<\/p>\n<p>The API for localStorage is incredibly simple. You set and get data like this:<\/p>\n<pre><code>\/\/ Set something\nlocalStorage.setItem(\"yourKey\", \"yourValue\");\n\/\/ Get something\nlocalStorage.getItem(\"yourKey\");\n<\/code><\/pre>\n<p>LocalStorage has a <code>setItem<\/code> method that you pass two strings to. The first is the name of the key you want to store the data with and the second string is the actual string you want to store. The <code>getItem<\/code> method takes a string as an argument that returns to you whatever is stored under that key in localStorage. Nice and simple.<\/p>\n<p>However, amongst the reasons to not use localStorage is the fact that <em>everything<\/em> has to be saved as a \u2018string\u2019. This means you can\u2019t directly store something like an array or object. For example, try running these commands in your browser console:<\/p>\n<pre><code>\/\/ Set something\nlocalStorage.setItem(\"myArray\", [1, 2, 3, 4]);\n\/\/ Get something\nlocalStorage.getItem(\"myArray\"); \/\/ Logs \"1,2,3,4\"\n<\/code><\/pre>\n<p>Even though we tried to set the value of \u2018myArray\u2019 as an array; when we retrieved it, it had been stored as a string (note the quote marks around \u20181,2,3,4\u2019).<\/p>\n<p>You can certainly store objects and arrays with localStorage but you need to be mindful that they need converting back and forth from strings.<\/p>\n<p>So, in order to write state data into localStorage it was written to a string with the <code>JSON.stringify()<\/code> method like this:<\/p>\n<pre><code>const storage = window.localStorage;\nstorage.setItem(\"players\", JSON.stringify(io.items));\n<\/code><\/pre>\n<p>When the data needed retrieving from localStorage, the string was turned back into usable data with the <code>JSON.parse()<\/code> method like this:<\/p>\n<pre><code>const players = JSON.parse(storage.getItem(\"players\"));\n<\/code><\/pre>\n<p>Using <code>localStorage<\/code> meant everything was on the client and that meant no 3rd party services or data storage concerns.<\/p>\n<p>Data was now persisting refreshes and sessions \u2014 Yay! The bad news was that localStorage does not survive a user emptying their browser data. When someone did that, all their In\/Out data would be lost. That\u2019s a serious shortcoming.<\/p>\n<p>It\u2019s not hard to appreciate that `localStorage` probably isn\u2019t the best solution for &#8216;proper&#8217; applications. Besides the aforementioned string issue, it is also slow for serious work as it blocks the &#8216;main thread&#8217;. Alternatives are coming, like <a href=\"https:\/\/developers.google.com\/web\/updates\/2019\/03\/kv-storage\">KV Storage<\/a> but for now, make a mental note to caveat its use based on suitability.<\/p>\n<p>Despite the fragility of saving data locally on a users device, hooking up to a service or database was resisted. Instead, the issue was side-stepped by offering a \u2018load\/save\u2019 option. This would allow any user of In\/Out to save their data as a JSON file which could be loaded back into the app if needed.<\/p>\n<p>This worked well on Android but far less elegantly for iOS. On an iPhone, it resulted in a splurge of text on screen like this:<\/p>\n<figure><a href=\"https:\/\/i0.wp.com\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/3109a5f6-bf60-4e06-b673-db30a946b272\/pwa-framework-saving-data-ios.png?ssl=1\"><\/p>\n<p>    <img data-recalc-dims=\"1\" decoding=\"async\" srcset=\"http:\/\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/07\/pwa-framework-saving-data-ios.png 400w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_800\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/3109a5f6-bf60-4e06-b673-db30a946b272\/pwa-framework-saving-data-ios.png 800w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1200\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/3109a5f6-bf60-4e06-b673-db30a946b272\/pwa-framework-saving-data-ios.png 1200w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1600\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/3109a5f6-bf60-4e06-b673-db30a946b272\/pwa-framework-saving-data-ios.png 1600w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_2000\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/3109a5f6-bf60-4e06-b673-db30a946b272\/pwa-framework-saving-data-ios.png 2000w\" src=\"https:\/\/i0.wp.com\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/07\/pwa-framework-saving-data-ios.png?w=900\" sizes=\"100vw\" alt=\"On an iPhone, it resulted in a splurge of text on screen\"><\/a><figcaption>\n      (<a href=\"https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/3109a5f6-bf60-4e06-b673-db30a946b272\/pwa-framework-saving-data-ios.png\">Large preview<\/a>)<br \/>\n    <\/figcaption><\/figure>\n<p>As you can imagine, I was far from alone in berating Apple via WebKit about this shortcoming. The relevant bug was <a href=\"https:\/\/bugs.webkit.org\/show_bug.cgi?id=167341\">here<\/a>.<\/p>\n<p>At the time of writing this bug has a solution and patch but has yet to make its way into iOS Safari. Allegedly, iOS13 fixes it but it\u2019s that\u2019s in Beta as I write.<\/p>\n<p>So, for my minimum viable product, that was storage addressed. Now it was time to attempt to make things more \u2018app-like\u2019!<\/p>\n<h4>App-I-Ness<\/h4>\n<p>Turns out after many discussions with many people, defining exactly what \u2018app like\u2019 means is quite difficult.<\/p>\n<p>Ultimately, I settled on \u2018app-like\u2019 being synonymous with a visual slickness usually missing from the web. When I think of the apps that feel good to use they all feature motion. Not gratuitous, but motion that adds to the story of your actions. It might be the page transitions between screens, the manner in which menus pop into existence. It\u2019s hard to describe in words but most of us know it when we see it.<\/p>\n<p>The first piece of visual flair needed was shifting player names up or down from \u2018In\u2019 to \u2018Out\u2019 and vice-versa when selected. Making a player instantly move from one section to the other was straightforward but certainly not \u2018app-like\u2019. An animation as a player name was clicked would hopefully emphasize the result of that interaction \u2013\u00a0the player moving from one category to another.<\/p>\n<p>Like many of these kinds of visual interactions, their apparent simplicity belies the complexity involved in actually getting it working well.<\/p>\n<p>It took a few iterations to get the movement right but the basic logic was this:<\/p>\n<ul>\n<li>Once a \u2018player\u2019 is clicked, capture where that player is, geometrically, on the page;<\/li>\n<li>Measure how far away the top of the area is the player needs to move to if going up (\u2018In\u2019) and how far away the bottom is, if going down (\u2018Out\u2019);<\/li>\n<li>If going up, a space equal to the height of the player row needs to be left as the player moves up and the players above should collapse downwards at the same rate as the time it takes for the player to travel up to land in the space vacated by the existing \u2018In\u2019 players (if any exist) coming down;<\/li>\n<li>If a player is going \u2018Out\u2019 and moving down, everything else needs to move up to the space left and the player needs to end up below any current \u2018Out\u2019 players.<\/li>\n<\/ul>\n<p><em>Phew!<\/em> It was trickier than I thought in English \u2014 never mind JavaScript!<\/p>\n<p>There were additional complexities to consider and trial such as transition speeds. At the outset, it wasn\u2019t obvious whether a constant speed of movement (e.g. 20px per 20ms), or a constant duration for the movement (e.g. 0.2s) would look better. The former was slightly more complicated as the speed needed to be computed \u2018on the fly\u2019 based upon how far the player needed to travel \u2014 greater distance requiring a longer transition duration.<\/p>\n<p>However, it turned out that a constant transition duration was not just simpler in code; it actually produced a more favorable effect. The difference was subtle but these are the kind of choices you can only determine once you have seen both options.<\/p>\n<p>Every so often whilst trying to nail this effect, a visual glitch would catch the eye but it was impossible to deconstruct in real time. I found the best debugging process was creating a QuickTime recording of the animation and then going through it a frame at a time. Invariably this revealed the problem quicker than any code based debugging.<\/p>\n<p>Looking at the code now, I can appreciate that on something beyond my humble app, this functionality could almost certainly be written more effectively. Given that the app would know the number of players and know the fixed height of the slats, it should be entirely possible to make all distance calculations in the JavaScript alone, without any DOM reading.<\/p>\n<p>It\u2019s not that what was shipped doesn\u2019t work, it\u2019s just that it isn\u2019t the kind of code solution you would showcase on the Internet. Oh, wait.<\/p>\n<p>Other \u2018app like\u2019 interactions were much easier to pull off. Instead of menus simply snapping in and out with something as simple as toggling a display property, a lot of mileage was gained by simply exposing them with a little more finesse. It was still triggered simply but CSS was doing all the heavy lifting:<\/p>\n<pre><code>.io-EventLoader {\n  position: absolute;\n  top: 100%;\n  margin-top: 5px;\n  z-index: 100;\n  width: 100%;\n  opacity: 0;\n  transition: all 0.2s;\n  pointer-events: none;\n  transform: translateY(-10px);\n  [data-evswitcher-showing=\"true\"] & {\n    opacity: 1;\n    pointer-events: auto;\n    transform: none;\n  }\n}\n<\/code><\/pre>\n<p>There when the <code>data-evswitcher-showing=\"true\"<\/code> attribute was toggled on a parent element, the menu would fade in, transform back into its default position and pointer events would be re-enabled so the menu could receive clicks.<\/p>\n<h5>ECSS Style Sheet Methodology<\/h5>\n<p>You\u2019ll notice in that prior code that from an authoring point of view, CSS overrides are being nested within a parent selector. That\u2019s the way I always favor writing UI style sheets; a single source of truth for each selector and any overrides for that selector encapsulated within a single set of braces. It\u2019s a pattern that requires the use of a CSS processor (Sass, PostCSS, LESS, Stylus, et al) but I feel is the only positive way to make use of nesting functionality.<\/p>\n<p>I\u2019d cemented this approach in my book, <a href=\"https:\/\/ecss.io\/\">Enduring CSS<\/a> and despite there being a plethora of more involved methods available to write CSS for interface elements, ECSS has served me and the large development teams I work with well since the approach was first documented way back in 2014! It proved just as effective in this instance.<\/p>\n<h4>Partialling The TypeScript<\/h4>\n<p>Even without a CSS processor or superset language like Sass, CSS has had the ability to import one or more CSS files into another with the import directive:<\/p>\n<pre><code>@import \"other-file.css\";\n<\/code><\/pre>\n<p>When beginning with JavaScript I was surprised there was no equivalent. Whenever code files get longer than a screen or so high, it always feels like splitting it into smaller pieces would be beneficial.<\/p>\n<p>Another bonus to using TypeScript was that it has a beautifully simple way of splitting code into files and importing them when needed.<\/p>\n<p>This capability pre-dated native JavaScript modules and was a great convenience feature. When TypeScript was compiled it stitched it all back to a single JavaScript file. It meant it was possible to easily break up the application code into manageable partial files for authoring and import then into the main file easily. The top of the main <code>inout.ts<\/code> looked like this:<\/p>\n<pre><code>\/\/\/ <reference path=\"defaultData.ts\" \/>\n\/\/\/ <reference path=\"splitTeams.ts\" \/>\n\/\/\/ <reference path=\"deleteOrPaidClickMask.ts\" \/>\n\/\/\/ <reference path=\"repositionSlat.ts\" \/>\n\/\/\/ <reference path=\"createSlats.ts\" \/>\n\/\/\/ <reference path=\"utils.ts\" \/>\n\/\/\/ <reference path=\"countIn.ts\" \/>\n\/\/\/ <reference path=\"loadFile.ts\" \/>\n\/\/\/ <reference path=\"saveText.ts\" \/>\n\/\/\/ <reference path=\"observerPattern.ts\" \/>\n\/\/\/ <reference path=\"onBoard.ts\" \/>\n<\/code><\/pre>\n<p>This simple house-keeping and organization task helped enormously.<\/p>\n<h5>Multiple Events<\/h5>\n<p>At the outset, I felt that from a functionality point of view, a single event, like \u201cTuesday Night Football\u201d would suffice. In that scenario, if you loaded In\/Out up you just added\/removed or moved players in or out and that was that. There was no notion of multiple events.<\/p>\n<p>I quickly decided that (even going for a minimum viable product) this would make for a pretty limited experience. What if somebody organized two games on different days, with a different roster of players? Surely In\/Out could\/should accommodate that need?<br \/>\nIt didn\u2019t take too long to re-shape the data to make this possible and amend the methods needed to load in a different set.<\/p>\n<p>At the outset, the default data set looked something like this:<\/p>\n<div>\n<pre><code>var defaultData = [\n  { name: \"Daz\", paid: false, marked: false, team: \"\", in: false },\n  { name: \"Carl\", paid: false, marked: false, team: \"\", in: false },\n  { name: \"Big Dave\", paid: false, marked: false, team: \"\", in: false },\n  { name: \"Nick\", paid: false, marked: false, team: \"\", in: false }\n];\n<\/code><\/pre>\n<\/div>\n<p>An array containing an object for each player.<\/p>\n<p>After factoring in multiple events it was amended to look like this:<\/p>\n<div>\n<pre><code>var defaultDataV2 = [\n  {\n    EventName: \"Tuesday Night Footy\",\n    Selected: true,\n    EventData: [\n      { name: \"Jack\", marked: false, team: \"\", in: false },\n      { name: \"Carl\", marked: false, team: \"\", in: false },\n      { name: \"Big Dave\", marked: false, team: \"\", in: false },\n      { name: \"Nick\", marked: false, team: \"\", in: false },\n      { name: \"Red Boots\", marked: false, team: \"\", in: false },\n      { name: \"Gaz\", marked: false, team: \"\", in: false },\n      { name: \"Angry Martin\", marked: false, team: \"\", in: false }\n    ]\n  },\n  {\n    EventName: \"Friday PM Bank Job\",\n    Selected: false,\n    EventData: [\n      { name: \"Mr Pink\", marked: false, team: \"\", in: false },\n      { name: \"Mr Blonde\", marked: false, team: \"\", in: false },\n      { name: \"Mr White\", marked: false, team: \"\", in: false },\n      { name: \"Mr Brown\", marked: false, team: \"\", in: false }\n    ]\n  },\n  {\n    EventName: \"WWII Ladies Baseball\",\n    Selected: false,\n    EventData: [\n      { name: \"C Dottie Hinson\", marked: false, team: \"\", in: false },\n      { name: \"P Kit Keller\", marked: false, team: \"\", in: false },\n      { name: \"Mae Mordabito\", marked: false, team: \"\", in: false }\n    ]\n  }\n];\n<\/code><\/pre>\n<\/div>\n<p>The new data was an array with an object for each event. Then in each event was an <code>EventData<\/code> property that was an array with player objects in as before.<\/p>\n<p>It took much longer to re-consider how the interface could best deal with this new capability.<\/p>\n<p>From the outset, the design had always been very sterile. Considering this was also supposed to be an exercise in design, I didn\u2019t feel I was being brave enough. So a little more visual flair was added, starting with the header. This is what I mocked up in Sketch:<\/p>\n<figure><a href=\"https:\/\/i0.wp.com\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/19df8a0e-d553-4984-b277-708c4178ee30\/01-pwa-article.png?ssl=1\"><\/p>\n<p>    <img data-recalc-dims=\"1\" decoding=\"async\" srcset=\"http:\/\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/07\/01-pwa-article.png 400w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_800\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/19df8a0e-d553-4984-b277-708c4178ee30\/01-pwa-article.png 800w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1200\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/19df8a0e-d553-4984-b277-708c4178ee30\/01-pwa-article.png 1200w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1600\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/19df8a0e-d553-4984-b277-708c4178ee30\/01-pwa-article.png 1600w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_2000\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/19df8a0e-d553-4984-b277-708c4178ee30\/01-pwa-article.png 2000w\" src=\"https:\/\/i0.wp.com\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/07\/01-pwa-article.png?w=900\" sizes=\"50vw\" alt=\"A mockup of the revised app design\"><\/a><figcaption>\n      Revised design mockup. (<a href=\"https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/19df8a0e-d553-4984-b277-708c4178ee30\/01-pwa-article.png\">Large preview<\/a>)<br \/>\n    <\/figcaption><\/figure>\n<p>It wasn\u2019t going to win awards but it was certainly more arresting than where it started.<\/p>\n<p>Aesthetics aside, it wasn\u2019t until somebody else pointed it out, that I appreciated the big plus icon in the header was very confusing. Most people thought it was a way to add another event. In reality, it switched to an \u2018Add Player\u2019 mode with a fancy transition that let you type in the name of the player in the same place the event name was currently.<\/p>\n<p>This was another instance where fresh eyes were invaluable. It was also an important lesson in letting go. The honest truth was I had held on to the input mode transition in the header because I felt it was cool and clever. However, the fact was it was not serving the design and therefore the application as a whole.<\/p>\n<p>This was changed in the live version. Instead, the header just deals with events \u2014 a more common scenario. Meanwhile, adding players is done from a sub-menu. This gives the app a much more understandable hierarchy.<\/p>\n<p>The other lesson learned here was that whenever possible, it\u2019s hugely beneficial to get candid feedback from peers. If they are good and honest people, they won\u2019t let you give yourself a pass!<\/p>\n<h4>Summary: My Code Stinks<\/h4>\n<p>Right. So far, so normal tech-adventure retrospective piece; these things are ten a penny on Medium! The formula goes something like this: the dev details how they smashed down all obstacles to release a finely tuned piece of software into the Internets and then pick up an interview at Google or got acqui-hired somewhere. However, the truth of the matter is that I was a first-timer at this app-building malarkey so the code ultimately shipped as the \u2018finished\u2019 application stunk to high heaven!<\/p>\n<p>For example, the Observer pattern implementation used worked very well. I was organized and methodical at the outset but that approach \u2018went south\u2019 as I became more desperate to finish things off. Like a serial dieter, old familiar habits crept back in and the code quality subsequently dropped.<\/p>\n<p>Looking now at the code shipped, it is a less than ideal hodge-bodge of clean observer pattern and bog-standard event listeners calling functions. In the main <code>inout.ts<\/code> file there are over 20 <code>querySelector<\/code> method calls; hardly a poster child for modern application development!<\/p>\n<p>I was pretty sore about this at the time, especially as at the outset I was aware this was a trap I didn\u2019t want to fall into. However, in the months that have since passed, I\u2019ve become more philosophical about it.<\/p>\n<p>The final post in this series reflects on finding the balance between silvery-towered code idealism and getting things shipped. It also covers the most important lessons learned during this process and my future aspirations for application development.<\/p>\n<div>\n  <img data-recalc-dims=\"1\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/07\/logo-red-20.png?w=900\" alt=\"Smashing Editorial\"><span>(dm, yk, il, ra)<\/span>\n<\/div>\n<\/article>\n<p class=\"wpematico_credit\"><small>Powered by <a href=\"http:\/\/www.wpematico.com\" target=\"_blank\">WPeMatico<\/a><\/small><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Designing And Building A Progressive Web Application Without A Framework (Part 2) Designing And Building A Progressive Web Application Without A Framework (Part 2) Ben Frain 2019-07-25T14:00:59+02:002019-07-26T11:06:48+00:00 The raison d\u2019\u00eatre of this adventure was to push your humble author a little in the disciplines of visual design and JavaScript coding. The functionality of the application&#8230;<a class=\"moretag\" href=\"https:\/\/www.taterboy.com\/blog\/2019\/07\/designing-and-building-a-progressive-web-application-without-a-framework-part-2\/\"> Read the full article&#8230;<\/a><\/p>\n","protected":false},"author":4,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"jetpack_post_was_ever_published":false},"categories":[371],"tags":[],"class_list":["post-68518","post","type-post","status-publish","format-standard","hentry","category-user-experience"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.0.1 - aioseo.com -->\n\t<meta name=\"description\" content=\"Designing And Building A Progressive Web Application Without A Framework (Part 2)Designing And Building A Progressive Web Application Without A Framework (Part 2) Ben Frain 2019-07-25T14:00:59+02:002019-07-26T11:06:48+00:00The raison d\u2019\u00eatre of this adventure was to push your humble author a little in the disciplines of visual design and JavaScript coding. The functionality of the application I\u2019d decided\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Guest Contribution\"\/>\n\t<meta name=\"keywords\" content=\"user experience\" \/>\n\t<link rel=\"canonical\" href=\"https:\/\/www.taterboy.com\/blog\/2019\/07\/designing-and-building-a-progressive-web-application-without-a-framework-part-2\/\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.0.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"en_US\" \/>\n\t\t<meta property=\"og:site_name\" content=\"Design for Immersive Technologies | User Experience for Games, Virtual Reality (VR) and Augmented\/Mixed Reality (AR\/MR)\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"Designing And Building A Progressive Web Application Without A Framework (Part 2) | Design for Immersive Technologies\" \/>\n\t\t<meta property=\"og:description\" content=\"Designing And Building A Progressive Web Application Without A Framework (Part 2)Designing And Building A Progressive Web Application Without A Framework (Part 2) Ben Frain 2019-07-25T14:00:59+02:002019-07-26T11:06:48+00:00The raison d\u2019\u00eatre of this adventure was to push your humble author a little in the disciplines of visual design and JavaScript coding. The functionality of the application I\u2019d decided\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/www.taterboy.com\/blog\/2019\/07\/designing-and-building-a-progressive-web-application-without-a-framework-part-2\/\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2019-07-26T11:19:18+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2019-07-26T11:19:18+00:00\" \/>\n\t\t<meta name=\"twitter:card\" content=\"summary\" \/>\n\t\t<meta name=\"twitter:title\" content=\"Designing And Building A Progressive Web Application Without A Framework (Part 2) | Design for Immersive Technologies\" \/>\n\t\t<meta name=\"twitter:description\" content=\"Designing And Building A Progressive Web Application Without A Framework (Part 2)Designing And Building A Progressive Web Application Without A Framework (Part 2) Ben Frain 2019-07-25T14:00:59+02:002019-07-26T11:06:48+00:00The raison d\u2019\u00eatre of this adventure was to push your humble author a little in the disciplines of visual design and JavaScript coding. The functionality of the application I\u2019d decided\" \/>\n\t\t<script type=\"application\/ld+json\" class=\"aioseo-schema\">\n\t\t\t{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/07\\\/designing-and-building-a-progressive-web-application-without-a-framework-part-2\\\/#article\",\"name\":\"Designing And Building A Progressive Web Application Without A Framework (Part 2) | Design for Immersive Technologies\",\"headline\":\"Designing And Building A Progressive Web Application Without A Framework (Part 2)\",\"author\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/author\\\/guestcontribution\\\/#author\"},\"publisher\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/#organization\"},\"image\":{\"@type\":\"ImageObject\",\"url\":\"https:\\\/\\\/i0.wp.com\\\/www.taterboy.com\\\/blog\\\/wp-content\\\/uploads\\\/2019\\\/07\\\/pwa-framework-saving-data-ios.png?fit=%2C&ssl=1\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/07\\\/designing-and-building-a-progressive-web-application-without-a-framework-part-2\\\/#articleImage\"},\"datePublished\":\"2019-07-26T04:19:18-07:00\",\"dateModified\":\"2019-07-26T04:19:18-07:00\",\"inLanguage\":\"en-US\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/07\\\/designing-and-building-a-progressive-web-application-without-a-framework-part-2\\\/#webpage\"},\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/07\\\/designing-and-building-a-progressive-web-application-without-a-framework-part-2\\\/#webpage\"},\"articleSection\":\"User Experience\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/07\\\/designing-and-building-a-progressive-web-application-without-a-framework-part-2\\\/#breadcrumblist\",\"itemListElement\":[{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog#listItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.taterboy.com\\\/blog\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/category\\\/user-experience\\\/#listItem\",\"name\":\"User Experience\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/category\\\/user-experience\\\/#listItem\",\"position\":2,\"name\":\"User Experience\",\"item\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/category\\\/user-experience\\\/\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/07\\\/designing-and-building-a-progressive-web-application-without-a-framework-part-2\\\/#listItem\",\"name\":\"Designing And Building A Progressive Web Application Without A Framework (Part 2)\"},\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog#listItem\",\"name\":\"Home\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/07\\\/designing-and-building-a-progressive-web-application-without-a-framework-part-2\\\/#listItem\",\"position\":3,\"name\":\"Designing And Building A Progressive Web Application Without A Framework (Part 2)\",\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/category\\\/user-experience\\\/#listItem\",\"name\":\"User Experience\"}}]},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/#organization\",\"name\":\"Design for Immersive Technologies\",\"description\":\"User Experience for Games, Virtual Reality (VR) and Augmented\\\/Mixed Reality (AR\\\/MR)\",\"url\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/author\\\/guestcontribution\\\/#author\",\"url\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/author\\\/guestcontribution\\\/\",\"name\":\"Guest Contribution\",\"image\":{\"@type\":\"ImageObject\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/07\\\/designing-and-building-a-progressive-web-application-without-a-framework-part-2\\\/#authorImage\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/d2aad73eb0f48c67b141e9fc978a0e498969791ed623346e361d02d19775b0bb?s=96&d=mm&r=g\",\"width\":96,\"height\":96,\"caption\":\"Guest Contribution\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/07\\\/designing-and-building-a-progressive-web-application-without-a-framework-part-2\\\/#webpage\",\"url\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/07\\\/designing-and-building-a-progressive-web-application-without-a-framework-part-2\\\/\",\"name\":\"Designing And Building A Progressive Web Application Without A Framework (Part 2) | Design for Immersive Technologies\",\"description\":\"Designing And Building A Progressive Web Application Without A Framework (Part 2)Designing And Building A Progressive Web Application Without A Framework (Part 2) Ben Frain 2019-07-25T14:00:59+02:002019-07-26T11:06:48+00:00The raison d\\u2019\\u00eatre of this adventure was to push your humble author a little in the disciplines of visual design and JavaScript coding. The functionality of the application I\\u2019d decided\",\"inLanguage\":\"en-US\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/#website\"},\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/07\\\/designing-and-building-a-progressive-web-application-without-a-framework-part-2\\\/#breadcrumblist\"},\"author\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/author\\\/guestcontribution\\\/#author\"},\"creator\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/author\\\/guestcontribution\\\/#author\"},\"datePublished\":\"2019-07-26T04:19:18-07:00\",\"dateModified\":\"2019-07-26T04:19:18-07:00\"},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/\",\"name\":\"Design for Immersive Technologies\",\"description\":\"User Experience for Games, Virtual Reality (VR) and Augmented\\\/Mixed Reality (AR\\\/MR)\",\"inLanguage\":\"en-US\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/#organization\"}}]}\n\t\t<\/script>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"Designing And Building A Progressive Web Application Without A Framework (Part 2) | Design for Immersive Technologies","description":"Designing And Building A Progressive Web Application Without A Framework (Part 2)Designing And Building A Progressive Web Application Without A Framework (Part 2) Ben Frain 2019-07-25T14:00:59+02:002019-07-26T11:06:48+00:00The raison d\u2019\u00eatre of this adventure was to push your humble author a little in the disciplines of visual design and JavaScript coding. The functionality of the application I\u2019d decided","canonical_url":"https:\/\/www.taterboy.com\/blog\/2019\/07\/designing-and-building-a-progressive-web-application-without-a-framework-part-2\/","robots":"max-image-preview:large","keywords":"user experience","webmasterTools":{"miscellaneous":""},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.taterboy.com\/blog\/2019\/07\/designing-and-building-a-progressive-web-application-without-a-framework-part-2\/#article","name":"Designing And Building A Progressive Web Application Without A Framework (Part 2) | Design for Immersive Technologies","headline":"Designing And Building A Progressive Web Application Without A Framework (Part 2)","author":{"@id":"https:\/\/www.taterboy.com\/blog\/author\/guestcontribution\/#author"},"publisher":{"@id":"https:\/\/www.taterboy.com\/blog\/#organization"},"image":{"@type":"ImageObject","url":"https:\/\/i0.wp.com\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/07\/pwa-framework-saving-data-ios.png?fit=%2C&ssl=1","@id":"https:\/\/www.taterboy.com\/blog\/2019\/07\/designing-and-building-a-progressive-web-application-without-a-framework-part-2\/#articleImage"},"datePublished":"2019-07-26T04:19:18-07:00","dateModified":"2019-07-26T04:19:18-07:00","inLanguage":"en-US","mainEntityOfPage":{"@id":"https:\/\/www.taterboy.com\/blog\/2019\/07\/designing-and-building-a-progressive-web-application-without-a-framework-part-2\/#webpage"},"isPartOf":{"@id":"https:\/\/www.taterboy.com\/blog\/2019\/07\/designing-and-building-a-progressive-web-application-without-a-framework-part-2\/#webpage"},"articleSection":"User Experience"},{"@type":"BreadcrumbList","@id":"https:\/\/www.taterboy.com\/blog\/2019\/07\/designing-and-building-a-progressive-web-application-without-a-framework-part-2\/#breadcrumblist","itemListElement":[{"@type":"ListItem","@id":"https:\/\/www.taterboy.com\/blog#listItem","position":1,"name":"Home","item":"https:\/\/www.taterboy.com\/blog","nextItem":{"@type":"ListItem","@id":"https:\/\/www.taterboy.com\/blog\/category\/user-experience\/#listItem","name":"User Experience"}},{"@type":"ListItem","@id":"https:\/\/www.taterboy.com\/blog\/category\/user-experience\/#listItem","position":2,"name":"User Experience","item":"https:\/\/www.taterboy.com\/blog\/category\/user-experience\/","nextItem":{"@type":"ListItem","@id":"https:\/\/www.taterboy.com\/blog\/2019\/07\/designing-and-building-a-progressive-web-application-without-a-framework-part-2\/#listItem","name":"Designing And Building A Progressive Web Application Without A Framework (Part 2)"},"previousItem":{"@type":"ListItem","@id":"https:\/\/www.taterboy.com\/blog#listItem","name":"Home"}},{"@type":"ListItem","@id":"https:\/\/www.taterboy.com\/blog\/2019\/07\/designing-and-building-a-progressive-web-application-without-a-framework-part-2\/#listItem","position":3,"name":"Designing And Building A Progressive Web Application Without A Framework (Part 2)","previousItem":{"@type":"ListItem","@id":"https:\/\/www.taterboy.com\/blog\/category\/user-experience\/#listItem","name":"User Experience"}}]},{"@type":"Organization","@id":"https:\/\/www.taterboy.com\/blog\/#organization","name":"Design for Immersive Technologies","description":"User Experience for Games, Virtual Reality (VR) and Augmented\/Mixed Reality (AR\/MR)","url":"https:\/\/www.taterboy.com\/blog\/"},{"@type":"Person","@id":"https:\/\/www.taterboy.com\/blog\/author\/guestcontribution\/#author","url":"https:\/\/www.taterboy.com\/blog\/author\/guestcontribution\/","name":"Guest Contribution","image":{"@type":"ImageObject","@id":"https:\/\/www.taterboy.com\/blog\/2019\/07\/designing-and-building-a-progressive-web-application-without-a-framework-part-2\/#authorImage","url":"https:\/\/secure.gravatar.com\/avatar\/d2aad73eb0f48c67b141e9fc978a0e498969791ed623346e361d02d19775b0bb?s=96&d=mm&r=g","width":96,"height":96,"caption":"Guest Contribution"}},{"@type":"WebPage","@id":"https:\/\/www.taterboy.com\/blog\/2019\/07\/designing-and-building-a-progressive-web-application-without-a-framework-part-2\/#webpage","url":"https:\/\/www.taterboy.com\/blog\/2019\/07\/designing-and-building-a-progressive-web-application-without-a-framework-part-2\/","name":"Designing And Building A Progressive Web Application Without A Framework (Part 2) | Design for Immersive Technologies","description":"Designing And Building A Progressive Web Application Without A Framework (Part 2)Designing And Building A Progressive Web Application Without A Framework (Part 2) Ben Frain 2019-07-25T14:00:59+02:002019-07-26T11:06:48+00:00The raison d\u2019\u00eatre of this adventure was to push your humble author a little in the disciplines of visual design and JavaScript coding. The functionality of the application I\u2019d decided","inLanguage":"en-US","isPartOf":{"@id":"https:\/\/www.taterboy.com\/blog\/#website"},"breadcrumb":{"@id":"https:\/\/www.taterboy.com\/blog\/2019\/07\/designing-and-building-a-progressive-web-application-without-a-framework-part-2\/#breadcrumblist"},"author":{"@id":"https:\/\/www.taterboy.com\/blog\/author\/guestcontribution\/#author"},"creator":{"@id":"https:\/\/www.taterboy.com\/blog\/author\/guestcontribution\/#author"},"datePublished":"2019-07-26T04:19:18-07:00","dateModified":"2019-07-26T04:19:18-07:00"},{"@type":"WebSite","@id":"https:\/\/www.taterboy.com\/blog\/#website","url":"https:\/\/www.taterboy.com\/blog\/","name":"Design for Immersive Technologies","description":"User Experience for Games, Virtual Reality (VR) and Augmented\/Mixed Reality (AR\/MR)","inLanguage":"en-US","publisher":{"@id":"https:\/\/www.taterboy.com\/blog\/#organization"}}]},"og:locale":"en_US","og:site_name":"Design for Immersive Technologies | User Experience for Games, Virtual Reality (VR) and Augmented\/Mixed Reality (AR\/MR)","og:type":"article","og:title":"Designing And Building A Progressive Web Application Without A Framework (Part 2) | Design for Immersive Technologies","og:description":"Designing And Building A Progressive Web Application Without A Framework (Part 2)Designing And Building A Progressive Web Application Without A Framework (Part 2) Ben Frain 2019-07-25T14:00:59+02:002019-07-26T11:06:48+00:00The raison d\u2019\u00eatre of this adventure was to push your humble author a little in the disciplines of visual design and JavaScript coding. The functionality of the application I\u2019d decided","og:url":"https:\/\/www.taterboy.com\/blog\/2019\/07\/designing-and-building-a-progressive-web-application-without-a-framework-part-2\/","article:published_time":"2019-07-26T11:19:18+00:00","article:modified_time":"2019-07-26T11:19:18+00:00","twitter:card":"summary","twitter:title":"Designing And Building A Progressive Web Application Without A Framework (Part 2) | Design for Immersive Technologies","twitter:description":"Designing And Building A Progressive Web Application Without A Framework (Part 2)Designing And Building A Progressive Web Application Without A Framework (Part 2) Ben Frain 2019-07-25T14:00:59+02:002019-07-26T11:06:48+00:00The raison d\u2019\u00eatre of this adventure was to push your humble author a little in the disciplines of visual design and JavaScript coding. The functionality of the application I\u2019d decided"},"aioseo_meta_data":{"post_id":"68518","title":null,"description":null,"keywords":null,"keyphrases":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_custom_url":null,"og_image_custom_fields":null,"og_custom_image_width":null,"og_custom_image_height":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"location":null,"local_seo":null,"created":"2021-02-07 18:46:11","updated":"2026-09-01 17:29:05","focus_keyword":null,"additional_keywords":null,"truseo_locale":null,"primary_term":null,"og_image_url":null,"og_image_width":null,"og_image_height":null,"twitter_image_url":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"Article","isEnabled":true},"graphs":[]},"limit_modified_date":false,"ai":null,"breadcrumb_settings":null,"seo_analyzer_scan_date":null},"aioseo_breadcrumb":"<div class=\"aioseo-breadcrumbs\"><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/www.taterboy.com\/blog\" title=\"Home\">Home<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">&raquo;<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/www.taterboy.com\/blog\/category\/user-experience\/\" title=\"User Experience\">User Experience<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">&raquo;<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\tDesigning And Building A Progressive Web Application Without A Framework (Part 2)\n\t\t<\/span><\/div>","aioseo_breadcrumb_json":[{"label":"Home","link":"https:\/\/www.taterboy.com\/blog"},{"label":"User Experience","link":"https:\/\/www.taterboy.com\/blog\/category\/user-experience\/"},{"label":"Designing And Building A Progressive Web Application Without A Framework (Part 2)","link":"https:\/\/www.taterboy.com\/blog\/2019\/07\/designing-and-building-a-progressive-web-application-without-a-framework-part-2\/"}],"jetpack_publicize_connections":[],"jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p8wzr5-hP8","jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/posts\/68518","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/comments?post=68518"}],"version-history":[{"count":0,"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/posts\/68518\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/media?parent=68518"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/categories?post=68518"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/tags?post=68518"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}