Over the summer, our department made a small but significant policy change. We decided to take a cue from Google’s 20% time philosophy and spend one day a week working on a Walker-related project of our choosing. Essentially, we wanted to embark on quicker, more nimble projects that hold more interest for our team. The project I decided to experiment with was making a mobile website for the Walker, m.walkerart.org.
Reviewing our current site to inform the mobile site
The web framework we use for most of our site has the ability, with some small changes, to load different versions of a page based on a visitor’s User Agent (what browser they’re using). This would mean we could detect if a visitor was running IE on a Desktop or Mobile Safari on an iPhone, and serve each of them two different versions of a page. This is how a lot of mobile sites are done.
This is not the approach we went with for our mobile site, because it violates two of the primary rules (in my mind) of making a mobile website:
- Make it simple.
- Give people the stuff they’re looking for on their phones right away.
Our site is complicated: we have pages for different disciplines, a calendar with years of archives, and many specialty sites. Rule #1, violated. To address #2, I took a look at our web analytics to figure out what most people come to our site looking for. This won’t surprise anyone, but it’s hours, admission, directions, and what’s happening today at the Walker:

So it seems pretty clear those things should be up front. One of the other things you might want to access on a mobile is Art on Call. While Art on Call is designed primarily around dial-in access, there is also a website, but it isn’t optimized for the small screen of a smartphone. We have WiFi in most spaces within our building, so accessing Art on Call via an web interface and streaming audio via HTTP rather than POTS is a distinct possibility that I wanted to enable.
Prototyping with Google AppEngine
I decided to develop a quick prototype using Google AppEngine, thinking I’d end up using GAE in the end, too. Because this was a 20% time project, I had the freedom to do it using the technology I was interested in. AppEngine has the advantage of being something that isn’t hosted on our server, so there was no need to configure any complicated server stuff. In my mind, AppEngine is perfect for a mobile site because of the low bandwidth requirements for a mobile site. Google doesn’t provide a ton for free, but if your pages are only 20K each, you can fit quite a bit within the quotas they do give provide. AppEngine’s primary language is also python, a big plus, since python is the best programming language.
In about two days I built a proof of concept mobile site that displayed the big-ticket pages (hours, admission,etc.) and had a simple interface for Art on Call. Using iUi as a front-end framework was really, really useful here, because it meant that the amount of HTML/CSS/JS I had to code was super minimal, and I didn’t have to design anything.
I showed the prototype to Robin and she enthusiastically gave me the green light to work on it full-time.
Designing a mobile website
A strategy I saw when looking at mobile sites was to actually have two mobile sites: one for the A-grade phones (iPhone, Nokia S60, Android, Pre) and one for the B- and C-grade phones (Blackberry and Windows Mobile). The main difference between the two is the use of javascript and some more advanced layout. Depending on what version of Blackberry you look at, they have a pretty lousy HTML/CSS implementation, and horrendous or no javascript support.
To work around this, our mobile site does not use any javascript on most pages and tries to keep the HTML/CSS pretty simple. We don’t do any fancy animations to load between pages like iUi or jQtouch do: even on an iPhone those animations are slow. If you make your pages small enough, they should load fast enough and a transition is not necessary.
Designing mobile pages is fun. The size and interface methods for the device force you to re-think how to people interact and what’s important. They’re also fun because they’re blissfully simple. Each page on our mobile site is usually just a headline, image, paragraph or two, and some links. Laying out and styling that content is not rocket surgery.
Initially, when I did my design mockups in Photoshop, I wanted to use a numpad on the site for Art on Call, much like the iPhone features for making a phone call. There’s no easy input for doing this, but I thought it wouldn’t be too hard to create one with a little javascript (for those that had it). Unfortunately, due to the way touchscreen phones handle click/touch events in the browser, there’s a delay between when you touch and when the click event fires in javascript. This meant that it was possible to touch/type the number much faster than the javascript events fired. No go.
Instead, the latest versions of WebKit provide with a HTML5 input field with a type of “number”. On iPhone OS 3.1 and better, it will bring up the keypad already switched to the numeric keys. It does not do this on iPhone OS prior to 3.1. I’m not sure how Android and Pre handle it.


Comparing smartphones
Here’s a few screenshots of the site on various phones:



Not pictured is Windows Mobile, because it looks really bad.
A future post may cover getting all of these emulators up and running, because it’s not as straight easy as it should be. Working with the blackberry emulator is especially painful.
How our mobile site works
The basic methodology for our mobile site is to pull the data, via either RSS or XML from our main website, parse it, cache it, and re-template it for mobile visitors. Nearly all of the pages on our site are available via XML if you know how to look. Parsing XML into usable data is a computationally expensive task, so caching is very important. Thankfully, AppEngine provides easy access to memcache, so we can memcache the XML fetches and the parsing as much as possible. Here’s our simple but effective URL parse/cache helper function:
[python]
from google.appengine.api import urlfetch
from xml.dom import minidom
from google.appengine.api import memcache
def parse(url,timeout=3600):
memKey = hash(url)
r = memcache.get(‘fetch_%s’ % memKey)
if r == None:
r = urlfetch.fetch(url)
memcache.add(key="fetch_%s" % memKey, value=r, time=timeout)
if r.status_code == 200:
dom = memcache.get(‘dom_%s’ % memKey)
if dom == None:
dom = minidom.parseString(r.content)
memcache.add(key="dom_%s" % memKey, value=dom, time=timeout)
return dom
else:
return False
[/python]
Google AppEngine does not impose much of a structure for your web app. Similar to Django’s urls.py, you link regular expressions for URLS to class-based handlers. You can’t pass any variables beyond what’s in the URL or the WebOb to the request handler. Each handler will call a different method, depending if it’s a GET, POST, DELETE, http request. If you’re coming from the django world like me, this is not much of a big deal at first, but it gets tedious pretty fast. If I had it to do over again, I’d probably use app-engine-patch from the outset, and thus be able to use all the normal django goodies like middleware, template context, and way more configurable urls.
Within each handler, we also cache the generated data where possible. That is, after our get handler has run, we cache all the values that we pass to our template that won’t change over time. Here’s an example of the classes that handle the visit section of our mobile site:
[python]
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template
from google.appengine.api import memcache
from xml.dom import minidom
from google.appengine.api import memcache
from utils import feeds, parse, template_context, text
import settings
class VisitDetailHandler(webapp.RequestHandler):
def get(self):
url = self.request.get("s") + "?style=xml"
template_values = template_context.getTempalteValues(self.request)
path = settings.TEMPLATE_DIR + ‘info.html’
memKey = hash(url)
r = memcache.get(‘visit_%s’ % memKey)
if r and not settings.DEBUG:
template_values.update(r)
self.response.out.write(template.render(path, template_values))
else:
dom = parse.parse(url)
records = dom.getElementsByTagName("record")
contents = []
for rec in records:
title = text.clean_utf8(rec.getElementsByTagName(‘title’)[0].childNodes[0].nodeValue)
body = text.clean_utf8(rec.getElementsByTagName(‘body’)[0].childNodes[0].nodeValue)
contents.append({‘title’:title,’body’:body})
back = {‘href’:’/visit/#top’, ‘text’:’Visiting’}
cacheableTemplateValues = { "contents": contents,’back’:back }
memcache.add(key=’visit_%s’ % memKey, value={ "contents": contents,’back’:back }, time=7200)
template_values.update(cacheableTemplateValues)
self.response.out.write(template.render(path, template_values))
[/python]
Dealing with parsing XML via the standard DOM methods is a great way to test your tolerance for pain. I would use libxml and xpath, AppEngine doesn’t provide those libraries in their python environment.
Because the only part of Django’s template system that AppEngine uses is the template language, and nothing else, we have to roll our own helper functions for context. Meaning, if we want to pass a bunch variables by default to our templates, something easy in django, we have to do it a little differently with GAE. I set up a function called getTemplateValues, which we pass the WebOb request, and it ferrets out and organizes info we need for the templates, passing it back as a dict.
[python]
def ua_test(request):
uastring = request.headers.get(‘user_agent’)
uaDict = {}
if "Mobile" in uastring and "Safari" in uastring:
uaDict[‘isIphone’] = True
if ‘BlackBerry’ in uastring:
uaDict[‘isBlackBerry’] = True
return uaDict
def getTempalteValues(request):
myDict = {}
myDict.update(ua_test(request))
myDict.update(googleAnalyticsGetImageUrl(request))
return myDict
[/python]
In my next post, I’ll talk about how to track visitors on a mobile site using google analytics, without using javascript.
Get Walker Reader in your inbox. Sign up to receive first word about our original videos, commissioned essays, curatorial perspectives, and artist interviews.