Skip to content

Offline mobile web app with SammyJS

Fri 15th August 2014

Last week I went to the Great British Beer Festival ( and to help me track the beers I wanted from the 900 or so draught and bottle beers and ciders available, I built a little web app.

The GBBF beer selector doesn't really work on mobile...

The GBBF beer selector doesn’t really work on mobile…

Great British Beer Festival do provide a beer selector on their website, but much like most organisations who make a web-thing for marketing, they’ve completely missed the point: people want to peruse the list while they’re *at* the beer festival, and their site is rather unfriendly to use on smartphones. What is needed is a mobile web app.

Last year a few of us had a go at this. My approach was to build a jQuery bookmarklet on top of their beer list, to allow the user to mark beers as “wanted”, “drunk” and “unavailable”. The advantage of the bookmarklet approach was that I didn’t have to make a copy of their data (and all the IP issues that incurs), and I was just augmenting what was already there (so hopefully would be less work). The disadvantage was that it required me to be online to access their website, and the jQuery to filter through their rather div-heavy DOM was slow on my Nexus One!

This year my aim was to build a small, simple web app in as few files as possible, so that I could download it onto my phone and use it offline (i.e. in airplane mode). This would involve getting the data to store in the web app (I have a lot of screen-scraping PHP code already) and then building a single page web app to let the user browse the beers and mark them as “wanted”, “drunk”, “unavailable” and make notes.

Getting the data

Unfortunately, the Great British Beer Festival are not an open data company (other than accidentally leaving an Excel of the real ales on their site). This means I had to screen-scrape the website. I’ve done a lot of this in some other projects, so just adapted the PHP code I already had. This involved reading every row of the table on the GBBF site into an associative array, then spitting it back out as JSON ready to be loaded into my JavaScript web app.

This took 3 or so hours the night before I was meant to be going to the beer festival. I was running out of time, but I intentionally wanted to restrict how long I could spend on this ūüôā I went to bed and decided to do the front end of the web app in the morning.

Single Page App framework

Despite¬†never having built an offline web app, I knew I’d¬†need a framework to handle the displaying of different “pages”,¬†because there will in fact only be one web page. There are a bunch out there, but I wanted something that I could quickly pick up and build something with in about 3 hours: that significantly reduces the field! One thing that annoys me about software frameworks is how ridiculously complicated they are to start with. You typically have to read several days worth of tutorials, which explain in excruciating detail all the quirks and inconsistencies of that framework (as if you’re going to remember any of it)!

Basically all you need for a simple client-side web app!

SammyJS Hello, World: Basically all you need for a simple client-side web app!

With PHP I spent a while trying Zend, CodeIgniter and CakePHP, all of which suffer from this flaw, before stumbling upon FatFreeFramework (F3), which is now my PHP framework of choice. I’ve recently been learning Django, which is a little bit heavy, but I’ve not investigated simpler frameworks yet (though I hear that it’s probably flask or bottle). Now was the time to find one for JavaScript.¬†My initial searches brought up things like AngularJS, EmberJS and BackboneJS, but as always the ones people shout about most are the ones they spend 40 hours a week working with in their job, so they know all the ins and outs. Eventually I found SammyJS, which had a Hello, World of only 9 lines of code, on its homepage, which instantly showed that it did everything I needed: handle a GET request, load data from a JSON, and render all items with a template. Perfect!

I first followed the first part of the SammyJS tutorial, which walks through loading from JSON, displaying items using templates, and then displaying other pages. I implemented what I was reading, but using my data rather than their example data. This quickly got me a base for what I needed: listing all the beers that are available.

I then added the ability to search by style. This was just the code for displaying all the beers, with an if statement to only display ones of a chosen style. Which style was defined by a URL route parameter. After repeating this for country, I realised that the code is the same for bar, brewery, country and style, so I created a JSON file for each of these and generalised the code to load the JSON file for whichever category is in the URL.

I wanted to make it look not-too-terrible, so went to put in jQuery Mobile to make it really look like a¬†mobile app. However, when I added it in, it started constantly reloading the root of the web server! It turns out that jQuery Mobile is more than just a set of widgets, it is also a framework for¬†single-page apps (if only I’d known)! Anyway, that broke everything, so I immediately got rid of it and dropped in Bootstrap, so I could give the lists of links and buttons a more user-friendly look and feel.

Finally, I had to add the functionality for letting the user select which beers they want to try, have drunk and any notes they want to make. I had written most of this last year, using jQuery and HTML5 localStorage, so it should have been a pretty easy job to port it over. However, SammyJS doesn’t seem to let you use regular jQuery, which is a bit of a pain. I ended up using regular onclick and onblur attributes in the HTML to call functions I defined in my JavaScript. This got the job done, but it was a bit disappointing to not be able to attach the events programmatically.

While I’m on the subject, HTML5 localStorage is really lovely. It is so much nicer to use than cookies, and just involves reading and writing values in an object that acts like an associative array called localStorage, and that gets magically saved by the browser between visits to the page. Simple!

I ran into another peculiarity of SammyJS when trying to display wanted beers as a separate list from unwanted beers, on the same page. I tried duplicating the loop, just with a different if statement in each. However, this still displayed some of the unwanted beers interleaved with the wanted beers. I think this is because some of the actions in SammyJS are based on asynchronous tasks (like loading a JSON file), so you’re meant to chain them using .next(). However, some of the SammyJS functions don’t seem to return an object that has .next(), so I was unable to chain in the way I wanted. A related problem meant that I found it difficult to insert text into the page before loading the JSON file. I’m sure I can figure out how to fix both these problems, I just didn’t have the time to do so during¬†the development of this app!

The last thing I did before running out the door to get the train to London was to put it online and test it briefly on my smartphone. It looked like it worked, and the only thing I needed to tweak was the CSS to increase the size of the text a little and set the viewport width.

<meta name="viewport" content="width=device-width, initial-scale=1">

Webserver on my phone

A web server on your Android phone!

A web server on your Android phone!

On the train, I tested whether I could save the app and use it offline. My very quick search indicated that in Android you could save a bookmark to the home screen for offline use. This did not work. I’d hoped that the (almost) single file aspect would mean Chrome would easily cache it and give me access to it, but as soon as I turned on airplane mode, Chrome immediately refused to even load any page (because it’s offline)!

I tried downloading all the files from the web server (luckily I had left a zip of the directory on the server), and using the file:/// protocol but this seemed to stop any of the JavaScript working.

My final chance was to download a web server to run on my phone and host the files locally.¬†There is a web server called kWS on the Play Store, which is free and did exactly what I needed! It’s also one of the few apps I’ve ever installed that wanted permission to access only 2 things (files and network, obviously for a web server). Once setup, with the files in /sdcard/htdocs/, I could access my app at localhost:8080. Brilliant!

Offline App Cache

The palaver with having to host a web server on my phone got me thinking: given a settings file at a certain location relative to the web app (say app.offline in the same directory as the home page), I could build an app that downloads all the appropriate files, and then hosts them locally on a web server on the phone. This seemed a little overkill, as someone must have thought of this before, so I hunted around a little.

And of course this is already a solved problem! I first found an Adobe blog about taking web apps offline, and once I knew the name of the technique (“app cache manifest”) I managed to find an HTML5rocks¬†application cache tutorial which explained a few of the nuances and an offline HTML5 tutorial that explained some of the tricks to debugging. It’s dead easy: create a manifest file which lists the files that are required offline (in my case index.html, app.js and a few .json data files) and reference it in the <html> tag of your pages. The only downside is that you do have to manually list every file you want offline (wildcards don’t really work – it can if you have directory listing switched on, but it didn’t work for me), but you could write a server side script to generate this if you have lots of files.

It’s quite useful to know that in Chrome you can go to¬†chrome://appcache-internals/ to see whether your app has been cached, and to clear the cache after you’ve updated your app. The console in Chrome Inspector is also helpful, as it tells you each of the files it is caching, and if it fails at any of them, gives you an error message.


There we go. In less than a day’s work I managed to¬†learn a client-side app framework, build an app to meet my needs and figure out how to get it to work offline. Hopefully, this will be useful practice for future app development! Of course, some of this has built on previous skills I have learned (PHP web site scraping,¬†jQuery and localStorage), but I’m glad¬†I managed to¬†find the tools to use quite easily, wrangle them into what I wanted and have the chance to share those tools here!

SammyJS is pretty lovely. Unfortunately it seems to become a bit of a pain to do things that don’t fit exactly in its view of the world, but it does do those basics elegantly enough that I’d like to use it again. localStorage is brilliant for storing simple information between uses of the app. The application cache is such an easy way to save your app offline and keep using it when not connected to a network. The bits for building offline web apps are all there, and they’re really easy to pick up. My advice is to start with these, and learn something more complex when you need it.

My code is at¬† in case you want to check it out (note you’ll need to get SammyJS as I’ve not figured out the licensing yet). You can check out a working version of it here:¬†

From → Beer, Programming, The Web

  1. A List Apart has an entertaining treatment of the negatives of Application Cache, and how to use Local Storage to deal with them:

Trackbacks & Pingbacks

  1. Rendering template with array of data in SammyJS | Rikki Rants

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: