Offline PWAs: My Adventure Beyond the Basics

You can build web apps (really, fancy websites) that work offline. When you build one of these things, you can put your device into airplane mode, open your browser and navigate to your URL, and it’ll just work. Pretty cool!

This is the promise of Progressive Web Apps (PWAs): you can build web apps that work like native apps, including ability to run offline.

I’ve built simple offline PWAs in the past, and things worked fairly well.

But this week I needed to do something trickier.

I run MessianicChords.com, a guitar chord chart site for Messianic Jewish music, and I needed to make it work offline. I would soon be traveling to a Messianic music festival where there’s little to no internet connection , and, as a guitar player myself, I wanted to bring up MessianicChords and access the chord charts even while offline.

So I figured, let’s make MessianicChords work entirely offline. Fun!

But this was trickier and a real test of the web platform’s offline capabilities:

  • Lots of content. My site has thousands of chord charts, totalling in the hundreds of MB. I can’t just cache everything all at once.
  • iframes don’t work with service worker caching. Chord charts are .docx and .pdf documents hosted on Google Drive (example) and rendered via iframe Service worker cache doesn’t work here because iframes start a new browsing context separate from your service worker.
  • Search and filtering. My guitar chord site lets users search for chord charts by name, artist, or lyrics, and lets users filter by newest or by artist. How can we do this while offline? Service worker cache is insufficient here.
  • HTML templates reused across URLs. My site is a single page app (SPA), where an HTML template (say, ChordDetails.html) is reused across many URLs (/chords/1, /chords/2, etc.) How can we tell service worker to use a single cached resource across different URLs?

These are the challenges I encountered. I solved them (mostly!), and that’s what this post is about. If you’re interested in building offline-capable web apps, you’ll learn something from this post.

The Goal

Since there are thousands of chord charts — several hundred MB worth of data — I don’t want to cache everything all at once.

Rather, my goal is to make the web app available offline by caching all static assets, then cache any chord charts viewed while online.

Put it another way, any chord charts viewed while online becomes available offline.

Making the web app load offline

This is the easy part. You add a service worker to your site, and configure your service worker to cache HTML, JS, CSS, web fonts, and other static assets.

Most “make your PWA offline-capable” articles on the web cover this — but only this.

However, even this “easy” part is fraught with gotchas. Cache invalidation? Cache expiration? Cache warming? Cache first or network first? Offline fallback? Revision-based URLs? etc.

Having implemented such service workers by hand in the past, I now recommend never doing that. 😂 Instead, use Google’s Workbox recipes in your service worker to handle all this for you.

Workbox recipes are small snippets of code that do common offline- and cache-related behaviors.

For example, here’s the static resource cache recipe:

import {staticResourceCache} from 'workbox-recipes';
staticResourceCache();

What does staticResourceCache() do? It tells your service worker to respond to requests for static resources (CSS, JS, fonts, etc.), with aĀ stale-while-revalidateĀ caching strategy so those assets can be quickly served from the cache and be silently updated in the background. This means users get an instantaneous response from the cache. Meanwhile, the cached resource is refreshed in the background. Combine this with versioned resources (e.g. /scrips/main-hash123xyz.js) generated by Webpack, Rollup, or other build system, and you’ve got an automatic cache invalidation handled for you.

Workbox has a recipe for images (cache-first stategy with built-in expiration and cache pruning), a recipe for HTML pages (network-first with slow load time fallback), and more.

I use Workbox recipes in my service worker, and this makes my site work offline:

However, if we stopped there, you’ll notice that viewing a chord chart still fails:

Chord chart fails to load while offline

Well, crap.

We used Google Workbox and setup some recipes – shouldn’t the whole app work offline? Why is loading a chord chart failing?

iframes and service workers

The thousands of chord charts on MessianicChords are authored in .docx and .pdf format. There’s a reason for that: chord charts have special formatting (specifically, careful whitespacing) that needs to be preserved. Otherwise, you get a G chord playing over the wrong word, and now you’ve messed up your song:

Plus, the dozens of folks who contributed chord sheets to this prefer using these formats. 🤷‍♂️

Maybe in the future we migrate all of them to plain text/HTML; that would make them much easier to support offline. But for now, they use .docx and .pdf.

How do you display .docx and .pdf files on the web without using plugins or extensions?

With Google Docs iframes.

Google Docs does crazy work to render these on the web, no plugins required. (Under the hood, they’re converting these complex docs into raw HTML + CSS while meticulously preserving the formatting.)

So, MessianicChords embeds an iframe to load the .docx or .pdf in Google Docs.

What does that have to do with offline PWAs?

Your service worker can’t cache stuff from iframe loads. Viewing a chord chart on MessianicChords loads an iframe to a chord chart in Google Docs, but the request to this Google Docs URL isn’t cached by our service worker.

Why?

By design, iframes start a new browsing context. That means the service worker on MessianicChords doesn’t (and cannot) control the fetch requests the iframe makes to Google Docs.

End result is, my guitar chords site can’t load chord charts while offline. 😔

There is no magical way around this; it’s a deliberate limitation (feature?) of the web platform.

I considered some wild ideas to work around this. Could I statically cache the HTML and resources of the iframe and send it back with the chord chart from my own server? No, turns out Google Docs won’t work if not served from docs.google.com. This and other wild ideas I tried.

I finally settled on something of a low-tech solution: screenshots.

I created a service that would load the Google Doc in the browser, take a screenshot of that, and send that screenshot back with the chord chart. (Thanks, Puppeteer!)

When you view the chord chart, we load and cache the screenshot of the doc. When you’re offline, we render the cached screenshot instead.

It works pretty good! Here’s online and offline for comparison:

Not bad!

This approach does lose some fidelity: the user can’t select and copy text from the offline cached image, for example. However, the main goal of offline viewing is achieved.

Searching, filtering, and other dynamic functionality

We now have a web app that loads offline (thanks to service worker + Google Workbox recipes). And we can even view chord charts offline, thanks to caching screenshots of the docs.

If we stopped here, we’d unfortunately be missing some things. Specifically:

Search:

Searching “blessing” on MessianicChords returns chord charts with “blessing” in the title, artist, or lyrics. How can we make this work offline?

Filtering:

MessianicChords lets users filter chord charts by artist or song name, or order by recent. How can we make this work offline?

Making this sort of dynamic functionality work offline required additional work.

For search, we need to be able to search artists, song names, and lyrics. While we’re storing request/response for chord charts in the service worker cache, this is insufficient for our needs.

Why insufficient? Well, looking things up in the service worker cache typically requires sending in a request or URL from which the response is returned. But in the case of search, we have no URL or request; we’re just looking for data.

While theoretically I could fetch all chord charts from the cache, it felt like using the wrong tool for the job.

I briefly considered using the cheap and simple localStorage. But given my requirements of potentially thousands of chord charts, it too felt like the wrong tool. I also remembered localStorage has some performance issues and is intended for a few, small items, not the kind of stuff I’m storing.

If service worker cache and localStorage are both out, what’s our remaining options?

IndexedDB.

This is a full-blown indexed database built into the web platform with a many-readers-one-writer model. Its API is, like service worker, rather low-level. But it’s built for storing large(r) items and accessing them in a performant way. The right tool for this job.

I set out on implementing an IndexedDB-backed cache for chord charts. The finished product is chord-cache.ts: about 300 lines of code implementing various functionality of MessianicChords: searching, filtering, sorting chord charts.

Once implemented, I set out to make all my pages offline-aware

  • The home page with search box would be updated to search the cache if we’re offline, or send an search request to the API if we’re online
  • The artists page would be updated to query the cache if we’re offline, or query the API if we’re online
  • …and so on

Except this is quite redundant. I realized, “Why am I coding this up for every page? Can we hide this behind a service?”

Good old object-oriented programming to the rescue. Since all API requests were made through my chord-service.ts, I changed that class’s behavior to be cache-aware and offline-aware. The following diagram explains the change:

Sorry for the poor man’s diagram, but you get the picture. I made chord-service.ts call a ChordBackend interface. That interface has 2 implementations: one that hits our IndexedDB cache and another that hits our API. The former is used when we’re offline, the latter when we’re online.

This way, we don’t have to update any of our pages. The pages just talk to chord-service.ts like usual. Yay for polymorphism.

This means that only chord-service.ts needs to know when we’re offline. How does that work?

navigator.onLine and other lies

My first thought would be to use the built-in navigator.onLine API. There’s even an online/offline events paired with it to be notified when your online status changes. Perfect!

Except, these don’t really work in practice.

The thing is, “are you online?” isn’t a super easy question to answer. What I found was if my phone had zero bars out in podunk rural Iowa, I wasn’t really online, but navigator.onLine reported true. Gah!

I also saw weird things when testing offline via browser dev tools. I hit F12 -> Network -> Offline. Surely that would put us in offline mode, yes? Nope. Sometimes (not always?) navigator.onLine returned a false positive.

Even putting my iPhone in airplane mode was no guarantee navigator.onLine would give me a correct result. 😔

The documentation for navigator.onLine warns you about some of this:

In Chrome and Safari, if the browser is not able to connect to a local area network (LAN) or a router, it is offline; all other conditions return true. So while you can assume that the browser is offline when it returns a false value, you cannot assume that a true value necessarily means that the browser can access the internet. You could be getting false positives, such as in cases where the computer is running a virtualization software that has virtual ethernet adapters that are always “connected.” Therefore, if you really want to determine the online status of the browser, you should develop additional means for checking.

In Firefox and Internet Explorer, switching the browser to offline mode sends a false value. Until Firefox 41, all other conditions return a true value; testing actual behavior on Nightly 68 on Windows shows that it only looks for LAN connection like Chrome and Safari giving false positives.

MDN for navigator.onLine

“You should develop additional means for checking [online status].” 🙄

Yeah, that’s kinda what I had to do. I built online-detector.ts which basically just makes a no-op call to my API. If it fails, we’re offline.

Do I need to keep this offline status up-to-date?

Nah. For my purposes, we detect once and go from there. You need to reload the app to see a different offline status. That works for me. But if you need something better, you could periodically hit your API and fire an event as needed.

Pseudo full-text search with IndexedDB

The last challenge I encountered was full-text search. Now that we have our chord-cache.ts which caches chord charts, I could fetch them by name. But the name had to be exact.

Searching for “King” would not match the chord chart, “He is King“. That’s because of the way IndexedDB works. When querying an index, you can query by range or by exact value.

Query by range doesn’t work for my purposes. I could match everything up to “King” or everything after “King”, but not sentences that contain “King”.

Additionally, queries are case-sensitive by default.

To compensate for this, I created some additional indexes that stored all the words in the song title. “He is King” would store “he” and “king”. Kind of a poor man’s case-insensitive full-text search.

When the user queries for “King”, I convert it to lower case, then asynchronously query all my indexes for “king”. I feed all the results into a Set to remove duplicate results. Bingo, we have working(ish) offline search.’

HTML template reuse

When I viewed my service worker cache (F12 -> Application -> Cache Storage), I noticed an oddity: every chord chart route (e.g. https://messianicchords.com/ChordSheets/5697) had cached the same HTML template.

That’s because as a Single Page Application (SPA), we use an HTML template for all chord chart detail pages, asynchronously loading in the actual chord chart details.

Not a huge deal, but this means that if I cache 1000 chord charts, I’ll have the exact same HTML template in the service worker cache for each one. Wasteful.

Is there a way to tell our service worker cache, “Hey, if you come across /chords/123, use the same cached result from /chords/678”?

It turns out that yes, this is possible and is quite easy with Google Workbox custom plugins. Specifically, you can pass a function to Google Workbox’s various recipes to tell it cache keys to use. This lets me use the same cache key for all my chord chart details:

// Page cache recipe: https://developers.google.com/web/tools/workbox/modules/workbox-recipes#page_cache
pageCache({
  plugins: [{
      // We want to override cache key for
      //  - Artist page: /artist/Joe%20Artist
      //  - Chord details page: /ChordSheets/2630
      // Reason is, these pages are the same HTML, just different behavior.
      cacheKeyWillBeUsed: async function({request}) {
        const isArtistPage = !!request.url.match(/\/artist\/[^\/]+$/);
        if (isArtistPage) {
          return new URL(request.url).origin + "/artist/_";
        }
        const chordDetailsRegex = new RegExp(/\/ChordSheets\/[\w|\d|-]+$/, "i");
        const isChordDetailsPage = !!request.url.match(chordDetailsRegex);
        if (isChordDetailsPage) {
          return new URL(request.url).origin + "/ChordSheets/_"
        }

        return request.url;
      }
    }]
});

Here we’re using the Google Workbox pageCache recipe, which hits the network and falls back to the cache if the network is too slow to respond.

We pass a custom plugin (really, just a function) to the recipe. It defines a cacheKeyWillBeUsed function, which Workbox uses to determine cache key. In it, I say, “If we’re navigating to chord details page, just use “ChordSheets/_” as the cache key.”

I do the same for artist page, for the same reason.

End result is, we avoid hundreds or thousands of duplicates for chord details and artist pages.

Summary

It’s possible to build great offline web apps. For most apps, service worker will suffice.

For my purposes, I needed to go further: adding an IndexedDB for my web app to enable full offline support for dynamic functionality like searching, filtering, and sorting.

iframes pose a difficulty for making your app available offline, as they start a new browsing context unintercepted by your service worker. If you own the domain you’re iframing, you can still make it work. For apps like mine that iframe content on domains I don’t own (docs.google.com in my case), I had to workaround the issue by creating screenshots of documents and loading those while offline.

My app doesn’t let users create or update data, so I didn’t have to manage this while offline. But the web platform can handle that, too, via BackgroundSync.

Bottom line: making a PWA offline is entirely possible. I think it’s amazing I can write software that works online and offline whether on iOS, Android, Windows, Mac, and VR/AR devices, using just a single codebase built on web standards.

Make it modern: Web dev with RavenDB, Angular, TypeScript

Summary: A modern dev stack for modern web apps. See how I built a new web app using RavenDB, Angular, Bootstrap and TypeScript. Why these tools are an excellent choice for modern web dev.

image

Twin Cities Code Camp (TCCC) is the biggest developer event in Minnesota. Iā€™ve written about it before: itā€™s a free event where 500 developers descend on the University of Minnesota to attend talks on software dev, learn new stuff, have fun, and become better at the software craft.

I help run the event and this April, we are hosting our 20th event. 20 events in 10 years. Yes, weā€™ve been putting on Code Camps for a decade! Thatā€™s practically an eternity in internet years. Smile

For the 20th event, we thought it was time to renovate the website. Our old website had been around since about 2006 ā€“ a decade old ā€“ and the old site was showing its age:

Screenshot of old Code Camp website

It got the job done, but admittedly itā€™s not a modern site. Rather plain Jane; it didnā€™t reflect the awesome event that Code Camp is. We wanted something fresh and new for our 20th event.

On the dev side, everything on the old site was hard-coded ā€“ no database ā€“ meaning every time we wanted to host a new event, add speakers or talks or bios, we had to write code and add new HTML pages. We wanted something modern, where events and talks and speakers and bios are all stored in a database that drives the whole site.

Dev Stack

Taking at stab at rewriting the TCCC websiite, I wanted to really make it a web app. That is, I want it database-driven, I want some dynamic client-side functionality; things like letting speakers upload their slides, letting attendees vote on talks, having admins login to add new talks, etc. This requires something more than a set of static web pages.

Additionally, most of the people attending Code Camp will be looking at this site on their phone or tablet. I want to build a site that looks great on mobile.

To build amazing web apps, I turn to my favorite web dev stack:

  • RavenDB ā€“ the very best get-shit-done database. Forget tables and sprocs and schemas. Just store your C# objects without having to map them to tables and columns and rows. Query them with LINQ when youā€™re ready.
  • AngularJS ā€“ front-end framework for building dynamic web apps. Transforms your website from a set of static pages to a coherent web application with client-side navigation, routing, automatic updates with data-binding, and more awesomeness. Turns this:
    $(".contentWrapper.container").attr("data-item-text", "zanzibar");

    into this:

    this.itemText = "zanzibar";

     

  • Bootstrap ā€“ CSS to make it all pretty, make it consistent, and make it look great on mobile devices. Turns this: image
    ā€¦into this:
    image
  • TypeScript ā€“ JavaScript extended with optional types, classes, and features from the future. This lets me build clean, easily refactored, maintainable code that runs in your browser. So instead of this ugly JavaScript code:imageā€¦we instead write this nice modern JavaScript + ES6 + types code, which is TypeScript:

    image

  • ASP.NET Web API ā€“ Small, clean RESTful APIs in elegant, asynchronous C#. Your web app talks to these to get the data from the database and display the web app.
  • FontAwesome ā€“ Icons for your app. Turns this: image into this:image

I find these tools super helpful and awesome and Iā€™m pretty darn productive with them. Iā€™ve used this exact stack to build all kinds of apps, both professional and personal:

And a bunch of internal apps at my current employer, 3M, use this same stack internally. I find this stack lets me get stuff done quickly without much ceremony and I want to tell you why this stack works so well for me in this post.

RavenDB

Iā€™m at the point in my career that I donā€™t put up with friction. If there is friction in my workflow, it slows me down and I donā€™t get stuff done.

RavenDB is a friction remover.

My old workflow, when I was a young and naĆÆve developer, went like this:

  1. Hmm, I think I need a database.
  2. Well, Iā€™d guess Iā€™d better create some tables.
  3. I should probably create columns with the right types for all these tables.
  4. Now I need to save my C# objects to the database. I guess Iā€™ll use an object-relational mapper, like LINQ-to-SQL. Or maybe NHibernate. Or maybe Entity Framework.
  5. Now Iā€™ve created mappings for my C# objects to my database. Of course, I have to massage those transitions; you canā€™t really do inheritance or polymorphism in SQL. Heck, my object contains a List<string>, and even something that simple doesnā€™t map well to SQL. (Do I make those list of strings its own table with foreign key? Or combine them into a comma-separated single column, and rehydrate them on query? Orā€¦ugh.)
  6. Hmm, why is db.Events.Where(ā€¦) taking forever? Oh, yeah, I didnā€™t add any indexes. Crap, itā€™s doing a full table scan. Whatā€™s the syntax for that again?

This went on and on. It wasnā€™t until I tried RavenDB did I see that all this friction is not needed.

SQL databases were built for a different era in which disk space was at a premium; hence normalization and varchar and manual indexes. This comes at a cost: big joins, manual denormalization for speed. Often times on big projects, we have DBAs building giant stored procedures with all kinds of temp tables and weird optimization hacks to make shit fast.

Forget all that.

With Raven, you just store your stuff. Hereā€™s how I store a C# object that contains a list of strings:

db.Store(codeCampTalk);

Notice I didnā€™t have to create tables. Notice I didnā€™t have to create foreign key relationships. Notice I didnā€™t have to create columns with types in tables. Notice I didnā€™t have to tell it how to map codeCampTalk.Tags ā€“ a list of strings ā€“ to the database.

Raven just stores it.

And when weā€™re ready to query, it looks like this:

db.Query<Talk>().Where(t => t.Author == "Jon Skeet");

Notice I didnā€™t have to do any joins; unlike SQL, Raven supports encapsulation. Whether thatā€™s a list of strings, a single object inside another object, or a full list of objects. Honey Badger Raven donā€™t care.

And notice I didnā€™t have to create any indexes. Raven is smart about this: it creates an index for every query automatically. Then it uses machine learning ā€“ a kind of AI for your database ā€“ to optimize the ones you use the most. If Iā€™m querying for Talks by .Author, Raven keeps that index hot for me. But if I query for Talks by .Bio infrequently, Raven will devote server resources ā€“ RAM and CPU and disk ā€“ to more important things.

Itā€™s self optimizing. And itā€™s frigginā€™ amazing.

The end result is your app remains fast, because Raven is responding to how itā€™s being used and optimizing for that.

And I didnā€™t have to do anything make that happen. I just used it.

Zero friction. I love RavenDB.

If youā€™re doing .NET, there really is no reason you shouldnā€™t be using it. Having built about 10 apps in the last 2 years, both professional and side projects, I have not found a case where Raven is a bad fit. Iā€™ve stopped defaulting to crappy defaults. Iā€™ve stopped defaulting to SQL and Entity Framework. Itā€™s not 1970 anymore. Welcome to modern, flexible, fast databases that work with you, reduce friction, work with object oriented languages and optimize for todayā€™s read-heavy web apps.

AngularJS

In the bad olā€™ days, weā€™d write front-end code like this:

image

JavaScript was basically used to wire up event handlers. And weā€™d do some postback to the server, which reloaded the page in the browser with the new email address. Yay, welcome to 1997.

Then, we discovered JQuery, which was awesome. We realized that the browser was fully programmable and JQuery made it a joy to program it. So instead of doing postbacks and having static pages, we could just update the DOM, and the user would see the new email address:

image

And that was good for users, because the page just instantly updated. Like apps do. No postback and full page refresh; they click the button and instantly see the results.

This was well and good. Until our code was pretty ugly. I mean, look at it. DOM manipulation isnā€™t fun and itā€™s error prone. Did I mention it was ugly?

What if we could do something like this:

image

image

Whoa! Now anytime we update a JavaScript variable, .emailAddress, the DOM instantly changes! Magic!

No ugly DOM manipulation, we just change variables and the browser UI updates instantly.

This is data-binding, and when we discovered it in the browser, all kinds of frameworks popped up that let you do this data-binding. KnockoutJS, Ember, Backbone, and more.

This was all well and good until we realized that while data-binding is great, it kind of sucks that we still have full page reloads when moving from page to page. The whole app context is gone when the page reloads.

What if we could wrap up the whole time a user is using our web app into a cohesive set of views which the user navigates to without losing context. And instead of a mess of JavaScript, what if we made each view have its own class. That class has variables in it, and the view data-binds to those variables. And that class, weā€™ll call it a controller, loads data from the server using classes called services. Now weā€™re organized and cooking with gas.

Enter AngularJS. Angular makes it a breeze to build web apps with:

  • Client-side navigation. This means as the app moves between pages, say between the Schedule and Talks pages, your app hasnā€™t been blown away. You still have all the variables and state and everything still there.
  • Data-binding. You put your variables in a class called a controller:
    image
    ā€¦and then in your HTML, you data-bind to those variables:
    imageThen, any time you change the variables, the DOM ā€“ that is, the web browser UI, automatically updates. Fantastic.

Angular also add structure. You load data using service classes. Those services classes are automatically injected into your controllers. Your controllers tell the service classes to fetch data. When the data returns, you set the variable in your controller, and the UI automatically updates to show the data:

image

Nice clean separation of concerns. Makes building dynamic apps ā€“ apps where the data changes at runtime and the UI automatically shows the new data ā€“ a breeze.

TypeScript

JavaScript is a powerful but messy language. Conceived in a weekend of wild coding in the late ā€˜90s, it was built for a different era when web apps didnā€™t exist.

TypeScript fixes this. TypeScript is just JavaScript + optional types + features from the future. Where features from the future = ES6, ES7, and future proposals ā€“ things that will eventually be added to JavaScript, but you wonā€™t be able to use for 5, 10, 15 years. You can use them right now in TypeScript.

TypeScript compiles it all down to normal JavaScript that runs in everybodyā€™s browser. But it lets you code using modern coding practices like classes, lambdas, properties, async/await, and more. Thanks to types, it enables great tooling experiences like refactoring, auto-completion, error detection.

So instead of writing ugly JavaScript like this:
image

We can instead write concise and clean, intellisense-enabled TypeScript like this:

image

Ahhhā€¦lambdas, classes, properties. Beautiful. All with intellisense, refactoring, error detection. I love TypeScript.

There are few reasons to write plain JavaScript today. Itā€™s beginning to feel a lot like writing assembly by hand; ainā€™t nobody got time for that. Use modern language features, use powerful tooling to help you write correct code and find errors before your users do.

Bootstrap

You donā€™t need to drink $15 Frappamochachino Grandes to design elegant UIs.

Weā€™ve got code at our disposal that gives us a nice set of defaults, using well-known UI concepts and components to build great interfaces on the web.

Bootstrap, with almost no effort, makes plain old HTML into something more beautiful.

A plain old HTML table:
image

Add a Bootstrap class to the <table> and itā€™s suddenly looking respectable:

image

A plain HTML button:

image

Add one of a few button classes and things start looking quite good:

image

Bootstrap gives you a default theme, but you can tweak the existing theme or use some pre-built themes, like those at Bootswatch. For TwinCitiesCodeCamp.com, I used the free Superhero theme and then tweaked it to my liking.

Bootstrap also gives you components, pieces of combined UI to build common UI patterns. For example, here is a Bootstrap split-button with drop-down, a common UI pattern:
image

Bootstrap enables these components using plain HTML with some additional CSS classes. Super easy to use.

Bootstrap also makes it easy to build responsive websites: sites that look good on small phones, medium tablets, and large desktops.

Add a few classes to your HTML, and now your web app looks great on any device. For TwinCitiesCodeCamp, we wanted to make sure the app looks great on phones and tablets, as many of our attendees will be using their mobile devices at the event.

Hereā€™s TwinCitiesCodeCamp.com on multiple devices:

Large desktop:
image

iPad and medium tablets:
image

And on iPhone 6 and small phones:
image

This is all accomplished by adding a few extra CSS classes to my HTML. The classes are Bootstrap responsive classes that adjust the layout of your elements based on available screen real-estate.

Summary

RavenDB, AngularJS, TypeScript, Bootstrap,. Itā€™s a beautiful stack for building modern web apps.

TwinCitiesCodeCamp code is on GitHub.

My beautiful web development soup

Summary: Web development is chaotic, overwhelming, and beautiful. How many technologies does it take to build something useful on the web? You might be surprised. Advice for developers getting into web development.


3654636770_3b1a5d470bThis week I was working on my open source startup project, BitShuva Radio. I acquired a new client this week, but this client was unique because he’s a Java developer and wanted to know how to edit the open source code for his new radio station.

As I typed out my answer, it dawned on me that the sheer number of  technologies utilized in building a useful web application is staggering. That they actually work together is a testament of the beautiful soup that is web development today.

How many technologies does it take to build something useful on the web? Hereā€™s my answer:

  • I use TypeScript, rather than plain JavaScript, for all client-side code. A superset of JavaScript with optional types, TypeScript is a well-structured, typed language with excellent tooling. I use it for practical reasons: as the app grew in complexity, the need for a more structured language became apparent. I ported the JS to TypeScript last month, and Iā€™m quite pleased with the results.
  • KnockoutJS is used heavily absolutely relied on. BitShuva is a dynamic single page application, meaning weā€™re doing lots of dynamic updates to the UI in real time. Knockout lets us make dynamic UIs very easily without resorting to tons of DOM manipulation code. Instead of DOM manipulation, Knockout lets you change your objects, and your UI automatically updates. For example, songsOnScreen.push(new Song()) will automatically show the song in the UI, no jQuery DOM manipulation required. 
  • I use knockout.postbox for decoupled pub/sub communication between client-side code components. For example, when I hit the play button, the click handler doesnā€™t need to know about HTML5 audio or any infrastructure concerns. Instead, it calls ko.postbox.publish(ā€œPlayā€). Whoever is in charge of playing audio simply calls ko.postbox.subscribe(ā€œPlayā€, ā€¦). This way, the click handler and the HTML5 audio component donā€™t need to know about each other. Nice and clean. Bonus: it works with Knockout observables, so you can say currentSong.publishOn(ā€œSongChangedā€) to automatically publish messages on a particular topic.
  • UbaPlayer is used for playing HTML5 audio with a Flash fallback. 
  • For styling, we use LESS, the CSS superset. We use LESS because CSS is redundant; LESS lets us use variables and functions for code reuse, but still compiles down to plain CSS.
  • jQuery is still there, but not as much as you might think. Because KnockoutJS handles the DOM <ā€”> code interaction, jQuery is only needed in rare cases where we need special DOM manipulation (e.g. to fade in/out an element). We also use jQuery to do AJAX calls.
  • The site generally uses Twitter Bootstrap UI toolkit for styling and consistency across the UI.
  • Weā€™re using Google Web Fonts for typography. 
  • For images like thumb-up/down, play button, etc. these are actually font characters, from the special Font Awesome web font. Because it’s just a font, they scale infinitely without losing fidelity, and you can change their appearance (size, color, etc.) using plain old CSS stylings. Icon fonts are awesome.
  • We use Asp.NET MVC Razor view engine to actually render our UI. Because of heavy reliance on KnockoutJS, Razor isnā€™t needed much; itā€™s mostly just plain HTML that is dynamically swapped in through AJAX and KnockoutJS.
  • The server code is written in C# 5 using Asp.NET MVC 4 + MVC Web API
  • ASP.NET Web Optimization Framework is used for minifying and bundling the scripts and CSS.
  • The database is RavenDB, a modern, 2nd generation document database, easily the best NoSQL database in the .NET world. I use it because itā€™s blazing fast and simple to just start dumping objects into.
  • ReactiveExtensions (RX)  is used on the C# server end to treat events as observable streams, which can then write LINQ queries on top of. We also use its sister project, Interactive Extensions, for some useful extension methods to IEnumerable objects (such as .Do and .ForEach).
  • To load up the whole shebang and edit the project top-to-bottom, I use Visual Studio 2012 + TypeScript tools plugin (for editing/compiling TypeScript) + Web Essentials (LESS compilation, better tooling for HTML5 web standards, and more).

Holy cow!

Hereā€™s the final tally of languages, frameworks, and tools used:

  • 5 languages – C#, TypeScript, LESS, .cshtml Razor syntax, plus bits of plain JavaScript here and there
  • 5 UI JavaScript frameworks ā€“ KnockoutJS, jQuery, UbaPlayer, Knockout.postbox, Twitter Bootstrap.
  • 6 server-side .NET frameworks ā€“ RavenDB, MVC 4, MVC Web API, Reactive Extensions, Interactive Extensions, Web Optimization Framework.
  • 3 tools ā€“ Visual Studio 2012, TypeScript tools plugin, Web Essentials plugin.

The sheer number of tools required to really build something useful on the web is staggering. You get the sense of why desktop developers — who often work in just a single language + UI framework (e.g. C# + WPF) — are hesitant to embrace the web. It requires massive retooling.

And to be truthful, this is only my beautiful web soup. I chose C# + KnockoutJS + TypeScript + LESS, but other web devs might prefer Ruby + BackboneJS + CoffeeScript + SASS. Or something entirely different.

The great thing is, there is so much evolution going on in web development space today, and this produces unique and specialized toolsets that help move the web forward. For example, languages like CoffeeScript and TypeScript are influencing the future direction of JavaScript.

Itā€™s a fun time to be in web development.

My advice for developers? Learn a few web technologies that pique your interest and cook up your own beautiful web soup.