Back to the homepage

Recent diary entries

Fun with Django storage backends

Django abstracts file storage using storage backends, from simple filesystem storage to things like S3. This can be used for processing file uploads, storing static assets, and more. This is just a brief look at some things you can do which are kind of fun.

Using Amazon S3

django-storages is “a collection of custom storage backends”, including support for Amazon S3. You want to use the boto-based one, because it has lots of useful features. You can use it pretty quickly without customisation just by adding a few variables to your settings.py; I tend to put AWS access keys in environment variables rather than have different settings.py for different uses, because it plays better with Heroku.

AWS_ACCESS_KEY_ID = os.environ['AWS_ACCESS_KEY_ID']
AWS_SECRET_ACCESS_KEY = os.environ['AWS_SECRET_ACCESS_KEY']
AWS_STORAGE_BUCKET_NAME = os.environ['AWS_STORAGE_BUCKET_NAME']
AWS_QUERYSTRING_AUTH = False
AWS_HEADERS = {
  'Cache-Control': 'max-age=86400',
}
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto.S3BotoStorage'
STATICFILES_STORAGE = 'storages.backends.s3boto.S3BotoStorage'
# these next two aren't used, but staticfiles will complain without them
STATIC_URL = "https://%s.s3.amazonaws.com/" % os.environ['AWS_STORAGE_BUCKET_NAME']
STATIC_ROOT = ''

DEFAULT_FILE_STORAGE is used when you want to store file-like things attached to your models, using field types like FileField and ImageField; STATICFILES_STORAGE is where the static files pulled together from apps and your project by the collectstatic command end up.

Okay, great. But say we want to do more?

Put static files in a slightly different place

If you subclass the S3BotoStorage class, you can override some of its configuration. There are lots of these, but location is an interesting one because it acts as a prefix for the keys stored in S3.

import storages.backends.s3boto

class PrefixedStorage(storages.backends.s3boto.S3BotoStorage):
  def __init__(self, *args, **kwargs):
    from django.conf import settings
    kwargs['location'] = settings.ASSETS_PREFIX
    return super(PrefixedStorage, self).__init__(*args, **kwargs)

So if we plonk a suitable bit of configuration into our settings.py:

ASSETS_PREFIX = 'assets'
STATICFILES_STORAGE = 'prefixed_storage.PrefixedStorage'

then our assets will be separated from our uploaded media. (You could also put them in a different bucket, using the bucket argument, for which you might also want to set access_key and secret_key differently to the default configuration we put in settings.py earlier.)

Protect some file storage

Most of your media uploads – user avatars, for instance – you want to be public. But if you have some media that requires authentication before you can access it – say PDF resumes which are only accessible to members – then you don’t want S3BotoStorage’s default S3 ACL of public-read. Here we don’t have to subclass, because we can pass in an instance rather than refer to a class.

from django.db import models
import storages.backends.s3boto

protected_storage = storages.backends.s3boto.S3BotoStorage(
  acl='private',
  querystring_auth=True,
  querystring_expire=600, # 10 minutes, try to ensure people won't/can't share
)

class Profile(models.Model):
  resume = models.FileField(
    null=True,
    blank=True,
    help_text='PDF resume accessible only to members',
    storage=protected_storage,
  )

There is no permanent publicly-accessible URL for the uploaded resumes, but it’s easy to write a view that will redirect to a temporary URL. Because we set up S3BotoStorage to use query string-based authentication, when asked for the field’s URL it will contact S3 and ask for a temporary one to be created. The configuration above gives use 600 seconds, or 10 minutes, before that URL becomes invalid and can no longer be used.

from django.views.generic import DetailView
from django.http import HttpResponseForbidden, HttpResponseNotFound, HttpResponseRedirect

class ResumeView(DetailView):
  model = Profile

  def get(self, *args, **kwargs):
    obj = super(ResumeView, self).get_object()
    if not request.user.is_authenticated():
      return HttpResponseForbidden()
    if obj.resume is None:
      return HttpResponseNotFound()
    return HttpResponseRedirect(obj.resume.url)

Or you could just put it in a template, only for members:

{% if user.is_authenticated %}
  <a href='{{ profile.resume.url }}'>Grab my resume</a>
{% endif %}

Making a staging version of your live database

This is something I needed to do recently for NSFWCORP: come up with an easy way of taking a live database dump and making a staging instance out of it. This is all run on Heroku, so moving the database dumps around is easy, and writing something to throw away all non-staff users, old conversation threads and so on is also simple. But I also needed to duplicate the media files from the live bucket to the staging bucket. My solution is as follows:

import os
import os.path
import shutil
import sys

from django.conf import settings
from django.core.management.base import BaseCommand
from django.db.models import get_models, FileField
from storages.backends.s3boto import S3BotoStorage


class Command(BaseCommand):
  output_transaction = True

  def handle(self, *args, **options):
    # we want a django-storages s3boto backend for live, using
    # a dedicated read-only key pair
    storage = S3BotoStorage(
      bucket='nsfw-live',
      access_key=settings.LIVE_READ_ONLY_ACCESS_KEY_ID,
      secret_key=settings.LIVE_READ_ONLY_SECRET_KEY,
    )
    # now just go through all the models looking for stuff to do
    for model in get_models():
      fields = filter(lambda x: isinstance(x, FileField), model._meta.fields)
      if len(fields) > 0:
        sys.stdout.write(u"Copying media for %s..." % model._meta.object_name)
        sys.stdout.flush()
        for obj in model.objects.all():
          for field in fields:
            _if = None
            _of = None
            _file = getattr(obj, field.name)
            if not _file.name:
              continue
            try:
              _if = storage.open(_file.name, 'rb')
              if not settings.AWS_AVAILABLE:
                full_path = _file.path
                directory = os.path.dirname(full_path)
                if not os.path.exists(directory):
                  os.makedirs(directory)
                if not os.path.exists(full_path):
                  with open(full_path, 'wb'):
                    pass
              _of = _file.storage.open(_file.name, 'wb')
              shutil.copyfileobj(_if, _of)
            except Exception as e:
              sys.stdout.write(u"\n  failed %s(pk=%i).%s = %s: " % (
                model._meta.object_name,
                obj.pk,
                field.name,
                _file.name
              ))
              sys.stdout.write(unicode(e))
            finally:
              if _if is not None:
                _if.close()
              if _of is not None:
                _of.close()
        sys.stdout.write("done.\n")

Note that there are three new settings.py variables: LIVE_READ_ONLY_ACCESS_KEY_ID and LIVE_READ_ONLY_SECRET_KEY should be fairly obvious, and AWS_AVAILABLE just tells me whether AWS support is configured in the environment, which I use to ensure the destination path and file exist in advance for local storage. I could avoid that by doing something like _file.save(_file.name, _of), although I’m not entirely sure that will preserve file paths and names. It’s cleaner though, and is probably a better solution.

Summing up

The Django storage API and pluggable backends gives a lot of flexibility in how you manage both static assets and file-like things. As well as django-storages there are plenty of other options for when the built-in file system options aren’t suitable for you.

Running statsd on Heroku

statsd is a “simple daemon for easy stats aggregation”: you send it stats whenever you can (such as when rendering a web page), and it aggregates them internally and passes them upstream to something that can store them and make them available for other clients for analysis, graphing and so on. Upstream stores from statsd might include the Carbon storage engine from Graphite that you can run yourself somewhere, or a hosted service such as Librato. You can combine the two by using Hosted Graphite, which does exactly what it says on the tin.

Heroku is an infrastructure as a service company that provides an abstraction over servers, virtual machines and so forth geared to web deployment, as well as a toolchain for working with that.

It would be nice if we could use them together, and the good news is that we can. I wrote this because I couldn’t find anything online that spells out how to. The code and configuration is available on github.

How we’re going to do this

A simple deployment of statsd is this: put one instance on each physical machine you have, and point them all at a storage system. (You can also chain instances together, and have instances send their data on to multiple receivers. Let’s just ignore all of that, because then you probably don’t want to host on Heroku, and if you do you can certainly figure out how this all applies to your setup.)

On Heroku, we don’t have physical machines; in fact there isn’t the concept of “machine” at all. Instead, Heroku has Dynos, which are described as “lightweight containers” for UNIX processes. From their documentation:

[A Dyno] can run any command available in its default environment combined with your app’s slug

(The slug is basically your codebase plus dependencies.)

When working with physical machines there’s a tendency to put a number of different types of process on each, to avoid having to buy and manage more of them. With virtualisation, and hosting systems such as Amazon EC2, this isn’t so important, and with Heroku their entire architecture is set up almost to mandate that you have different types of Dynos (called process types) for different jobs; almost always a web type that is basically your application server, probably a secondary worker type that handles any long-running operations asynchronously to web requests, and so on.

However this doesn’t mean we can’t run multiple UNIX processes within one Dyno. Providing each process type is still only doing one thing, it still fits the Heroku semantics. This means we can tuck a statsd instance away in each Dyno, so it will aggregate information from the work being done there, with each statsd sending its aggregated data upstream.

(Why not have a process type for statsd and send all data to one or two Dynos before aggregating it upstream? Because statsd works over UDP for various sound reasons, but Heroku doesn’t provide UDP routing for its Dynos. Even if it did, you wouldn’t want to do things that way because UDP between arbitrary Dynos running who knows where within Heroku’s virtualised infrastructure can fall foul of all sorts of intermediate network issues.)

A demonstration app

Process types are configured in your app’s Procfile, so we want a single command that launches both statsd and whatever the main work of this Dyno is going to be. Let’s start by making a simple Flask app and deploying it to Heroku without statsd.

# requirements.txt
Flask==0.9
gunicorn==0.17.2

# web.py
from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"

if __name__ == "__main__":
    app.run()

And a simple Procfile to launch that:

# Procfile
web: gunicorn -b "0.0.0.0:$PORT" -w 4 web:app

If we turn this into a git repo, create a Heroku app and push everything up, we’ll be able to see our very boring homepage.

$ git init
$ git add requirements.txt Procfile web.py
$ git commit -a -m 'Simple Flask app for Heroku.'
$ heroku apps:create
Creating afternoon-reaches-9313... done, stack is cedar
http://afternoon-reaches-9313.herokuapp.com/ | git@heroku.com:afternoon-reaches-9313.git
Git remote heroku added
$ git push heroku master

(Lots of unimportant output removed; the important bit is the output from heroku apps:create which tells you the URL.)

Okay, all is well there. Let’s get statsd into play.

Doing two things at once in a Dyno

The key here is to put a command in the Procfile which launches both gunicorn and the statsd. A simple choice here is honcho, which is a python version of foreman. (If we were doing this using the Heroku Ruby runtime (say a Rails or Sinatra app) then it would make sense to use foreman instead.)

As we’re working in the python side of things, let’s add a simple statsd counter to our web app at the same time.

# requirements.txt
Flask==0.9
gunicorn==0.17.2
honcho==0.4.0
python-statsd==1.5.8

# web.py
import statsd
from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    counter = statsd.Counter("Homepage hits")
    counter += 1
    return "Hello World!"

if __name__ == "__main__":
    app.run()

Honcho uses a Procfile itself to figure out what to launch, so we need to give it separate configuration from the main Heroku one:

# Procfile.chain
web: gunicorn -b "0.0.0.0:$PORT" -w 4 web:app
statsd: cat /dev/zero

At this point we don’t know how to launch a statsd so we’ll just have it launch a dummy command that will keep running while gunicorn does its work. Then we need the main Heroku Procfile to launch honcho instead of gunicorn directly:

# Procfile
web: USER=nobody PORT=$PORT honcho -f Procfile.chain start

(The USER environment variable is needed because of how honcho defaults some of its options.)

And push it to Heroku:

$ git add requirements.txt Procfile Procfile.chain  web.py 
$ git commit -a -m 'Run gunicorn + dummy process; python will try to push to statsd'
$ git push heroku master

The python that tries to push a counter to statsd will fail silently if there isn’t one running, so all is well and you should still be able to get to your homepage at whichever URL Heroku gave you when you created the app.

Running statsd on Heroku

statsd is a node.js program, so we want the Heroku node.js support in order to run it. Heroku supports different languages using buildpacks – and we’re already using the Python buildpack to run Flask. Fortunately there are community-contributed buildpacks available, one of which suits our needs: heroku-buildpack-multi allows using multiple buildpacks at once. We need to set this as the buildpack for our app:

$ heroku config:add BUILDPACK_URL=https://github.com/ddollar/heroku-buildpack-multi.git

Then we can add a .buildpacks file that lists all the buildpacks we want to use.

http://github.com/heroku/heroku-buildpack-nodejs.git
http://github.com/heroku/heroku-buildpack-python.git

The node.js buildpack uses package.json to declare dependencies:

/* package.json */
{
  "name": "heroku-statsd",
  "version": "0.0.1",
  "dependencies": {
    "statsd": "0.6.0"
  },
  "engines": {
    "node": "0.10.x",
    "npm":  "1.2.x"
  }
}

statsd itself needs a tiny amount of configuration; at this point we’re not going to consider an upstream, so we want it to log every message it gets sent so we can see it in the Heroku logs:

/* statsd-config.js */
{
  dumpMessages: true
}

And finally we want to chain Procfile.chain so honcho knows to launch statsd:

web: gunicorn -b "0.0.0.0:$PORT" -w 4 web:app
statsd: node node_modules/statsd/stats.js statsd-config.js

Push that up to Heroku:

$ git add .buildpacks package.json statsd-config.js Procfile.chain
$ git commit -a -m 'Run statsd alongside gunicorn'
$ git push heroku master

If you hit your Heroku app’s URL you won’t see anything different, but when you check your Heroku logs:

$ heroku logs
2013-04-17T14:06:38.766960+00:00 heroku[router]: at=info method=GET path=/ host=afternoon-reaches-9313.herokuapp.com fwd="149.241.66.93" dyno=web.1 connect=2ms service=5ms status=200 bytes=12
2013-04-17T14:06:38.780056+00:00 app[web.1]: 14:06:38 statsd.1 | 17 Apr 14:06:38 - DEBUG: Homepage hits:1|c

Again I’ve removed a lot of boring output to focus on the two important lines: the first (from the Heroku routing layer; gunicorn itself doesn’t log by default) shows the request being successfully processed, and the second shows statsd getting our counter.

Pushing upstream

Both Librato and Hosted Graphite provide statsd backends so you can aggregate directly to them. For Librato the plugin is statsd-librato-backend, and for Hosted Graphite it’s statsd-hostedgraphite-backend. Other options will either have their own backends, or you can always write your own.

As well any configuration to support your chosen upstream, you probably want to drop the dumpMessages: true line so your Heroku logs are tidier.

Running locally

Everything we’ve done here will work locally as well. Assuming you have node.js (and npm) installed already, and you have virtualenv on your system for managing python virtual environments, just do:

$ virtualenv ENV
$ source ENV/bin/activate
$ ENV/bin/pip install -r requirements.txt
$ npm install
$ honcho -f Procfile.chain start

Caveats

I haven’t used this in production (yet), so beyond the concept being sound I can’t commit to its working without problems. In particular, things to think about include:

  • honcho isn’t usually used in production, so may have gotchas (note that if any process running under honcho quits the entire thing will shut down, which means the Dyno will die and be replaced; this is almost certainly what you want)
  • I don’t know how Dyno teardown works, and so statsd may lose data on Dyno cycle (which is rarely a huge problem)
  • Not actually tested on more than one dyno at once

Certainly if you put this into production I’d pay attention to Heroku platform errors, do spot checks on data coming out of statsd if you can, and generally be cautious.

Introducing Accessibility Advent

Recently I’ve noticed a particular kind of rage growing within me, a rage that has me tweeting bile and involuntarily punching imaginary developers sitting next to me. It is the rage caused by people who don’t think about accessibility when building (mostly) websites.

There are a million sites and articles that make the case that you should care about accessibility – features and techniques that help users of “assistive technology” – usually from a legal (local legislation requires you make efforts towards accessibility) or economic (you can make more money, yay!) standpoint. My argument is different.

I use assistive technology. When I encounter a poorly built website or piece of software, I wanted to punch the people involved. Eventually I won’t be able to constrain myself.

No one really wants that, so this Advent I’ll be giving some hints on how you, as a web developer, can make my life easier. I use Dragon Dictate for Mac, which uses the same voice recognition engine as Dragon Naturally Speaking for Windows; my advice will sometimes be specific to Dragon, and more commonly only considered from the point of view of speech recognition users – I’ll be trying to explain why these things are important to me, as well as what you should do. Nonetheless, the vast majority of things that people commonly get wrong are either general accessibility mistakes, or subtle details that are unlikely to cause problems for any other users if implemented the way I think they should be. If you disagree, or I’ve just got something utterly wrong, then let’s talk so I can improve my advice.

I’ll be publishing these weekdays during Advent. They aren’t in any particular order. I may forget to update the following list every day:

Accessibility Advent: normal accessibility advice still applies

(Throughout Advent I’m sharing some hints as to how web developers can make my life as a speech recognition user easier.)

It’s still advent, but lots of people have already started their trips to wherever they’re spending Christmas, so I just wanted to point out that a lot of the the normal accessibility advice helps voice users too. Nuance, who make Dragon, have guidelines for speech-accessible HTML which are worth looking at, even though they’re a few years old now, and based on the Windows version which has more features than the Mac version.

For a view of Dragon from the point of view of a web developer who just wanted to learn a little about Dragon and using it, check out Jon Whiting’s article from last year, Assistive Technology Experiment: Dragon NaturallySpeaking (he links to the same guidelines as above, although curiously under a different URL).

I’d recommend all web developers spend some time using either the Windows or Mac version; although it’s a significant amount of money to spend, it’s cheaper than some assistive technologies such as JAWS. If you’d like to have me come and talk to your company about using computers by voice, then please get in touch. I can of course include a demonstration (swearing as things go wrong strictly optional; please express preference at time of booking ;-), and if desired can perform a review in advance of a web site or app you’ve built, which can drive both demonstration and discussion.

Accessibility Advent: scrolling issues redux

(Throughout Advent I’m sharing some hints as to how web developers can make my life as a speech recognition user easier.)

Earlier this month I wrote about paging, suggesting that if you want floated information around your main content, the main content should scroll separately to the rest of your webpage. It turns out there’s a problem with this, although it is fixable. For a demonstration of what can go wrong, we turn to Quartz.

Looking at a Quartz story, there are three different things that might be scrollable – a list of topics that could scroll horizontally at the top, a list of stories that could scroll vertically down the left hand side and the story itself, on the right. (As I look at it now the main content is preceded in its scrollable area by an advert that takes up almost all of my screen. If I looked at this on a netbook, I wouldn’t have seen any content at all.)

You could, perhaps, make an argument for scrolling the stories list by default. Certainly there’s a strong argument for strolling the content itself by default. I think the topics list is fine as it is. There is no argument on earth that justifies what actually happens, which is that nothing scrolls by default.

What I mean is that pressing page up and page down, the keyboard access to scrolling (and, as it happens, the voice access as well), does absolutely nothing. Why not? Because the wrong thing has focus: I’m guessing the entire page (which is the default), but since that’s a fixed size filling the window, it won’t scroll. A simple touch of JavaScript will focus the correct scrollable area, and make life easier.

So really this is about focus, in which case I’ll take advantage of the opportunity to point out something nice I noticed about Barclays’ online banking system today. They have a fairly modern web app style, meaning that a lot of operations bring up a modal overlay to get them done. So making payments, transferring money and so on all have these little in-page dialogs. Not only do they support the escape key, they go one further when opening the overlays to make sure that focus is transferred to the first element of that overlay. This means you can tab through the controls on the overlay without worrying about what was behind it – avoiding a major annoyance in having overlays. They also have a number of other subtle features, such as showing jump links on focus.

Accessibility Advent: only I get to put stuff in text inputs

(Throughout Advent I’m sharing some hints as to how web developers can make my life as a speech recognition user easier.)

I wrote yesterday about enhancing long drop-down menus to turn them into combo boxes, which act more like text areas and so are somewhat more tractable to voice. However you can still screw them up; here are two ways I’ve seen recently.

The first is where you’re implementing auto complete on a text area. The best way of doing this is to provide a drop-down menu of possible completions and only fill the text area when one is explicitly selected. (This is how Google Search does it, for instance.) This means that until I finished dictating into the text area I can continue to use Dragon commands to correct that dictation. If you remember, Dragon maintains an internal view of what the text is in the current input field, so if you complete automatically in the text field this internal view is now incorrect. We might have the following, having dictated “James":

James Aylett

“James” has been input, but the web app has added “Aylett”, and the text cursor will be after that.

If I actually meant “chains”, and I use Dragon’s correction commands, Dragon will try to correct the word immediately before the text cursor, which it thinks is “James”, but which is actually “Aylett”. Dragon typically uses character by character selection, so what we are likely to end up with is something like “James Achains”.

Note that once the user has selected a completion from the menu, the text input is naturally going to contain some stuff that Dragon doesn’t know about. Voice users should be able to spot these kind of explicit situations, and have a command specifically to resynchronize Dragon’s view of an input, if they need to edit it further.

The other problem is more insidious, although I haven’t seen it in a web app as yet. It’s the way Google Chrome makes its address bar work. Someone on the Chrome team clearly decided that the “http://” part of the URL wasn’t necessary; other schemes are shown, so why take up space showing the most common? Except when you cut and paste the URL from Chrome it always includes the scheme, even if it’s HTTP. This is very clever, but trips up Dragon.

If you want to synchronize Dragon’s view of an input with what’s already there, you say “cache document”. It then selects all the text, and copies it into its own view; then it manually moves the cursor to the beginning then forward to the place where Dragon believes it to be. At this point (usually) Dragon and the input match each other, and voice editing commands will work smoothly.

But when the URL is copied out of Chrome’s address bar, the “http://” part is added to the front, meaning that Dragon thinks it’s there but Chrome, when editing commands are applied to the input field, does not. This creates a similar problem to the first example, in that trying to select parts of the text (to replace it, to add more before or after it, or to apply formatting commands such as capitalization) will select the wrong characters.

So with Google’s URL in the address bar (Google actually uses HTTPS for everything these days, but it’s easier to pretend it doesn’t for the purposes of explanation than to find a website that won’t move to HTTPS in the future), and Dragon thinking it’s synchronized, we might choose to go instead to Microsoft’s website, saying “select Google / Microsoft”. Dragon’s internal view is now “http://Microsoft.com”. However it will attempt to make that edit by selecting the eighth to thirteenth characters and typing “Microsoft” over them. Because the scheme isn’t in the actual text input, you end up with: “google.Microsoft”.

The only way of working round this is to copy the contents of the address bar out, edit them separately from Google Chrome, and then copy them back in again.

Accessibility Advent: enhance long drop downs

(Throughout Advent I’m sharing some hints as to how web developers can make my life as a speech recognition user easier.)

A simple one today: if you have long drop downs on any of your forms, such as if you have to ask for my country, then please use progressive enhancement to turn it into a combo box.

The problem is that your ordering of items within a drop-down is going to be wrong. You can do clever tricks – look up my likely country using geo-IP, then pull the most likely other options to the top before dropping to alphabetic order (although note that if you provide too many options out of order you make it harder for people to find the correct entry) – but anytime you have a large number of options in a list someone is going to get the short straw and have considerable difficulty.

For me this usually happens because “United Kingdom” comes in alphabetic order after “United Arab Emirates”, but people sometimes instead list it incorrectly as “Great Britain”. So I might select the drop-down (which usually does not open it), and say “United”, which will either select “United Kingdom” (if it’s pulled out towards the top of the list) or “United Arab Emirates” (if it’s not). If the latter, saying “Kingdom” will probably end up with the right option being selected, although if it’s in the list as “Great Britain” then all bets are off.

Worse, if “United Kingdom” is in the list and was selected to start off with but I didn’t notice, saying “United Kingdom” can end up with either “Iran” or “Kiribati” being selected (basically down to how much space I leave between the two words, although sometimes it actually selects the correct option). And if Dragon misrecognizes what I say, anything could be selected. My easiest choice at that point is to force the drop-down to open if it hasn’t already (by saying something like “down arrow") and trying to figure out by eye what the correct option is, hoping it’s actually on screen.

With a combo box, I can just say “United Kingdom” and, if Dragon misrecognizes my speech, I can use normal speech correction commands to make things right. Validation against the list should come as late as possible, to avoid messing with this process.

Accessibility Advent: beware focus events, redux

(Throughout Advent I’m sharing some hints as to how web developers can make my life as a speech recognition user easier.)

I wrote previously about being wary of the blur event; this hint is kind of the opposite. When you’re binding keystroke event handlers, be very careful about where you bind them to. This, at least, is what I think is happening with Google Instant.

If you have a reasonably modern computer you’ll see Instant working: on a Google results page if you type changes to your search and Google is confident that the results match what you’re looking for then the results will be updated “instantly”.

I have to turn it off. (All credit to Google for making this possible.)

The problem is that Instant interacts poorly with Glee Box; if I bring up Glee Box (typically by pressing g) then anything I “type” into it also gets added to the Google search box. Then Instant kicks in a couple of seconds later and completely changes the search results (it also usually removes the Glee Box, for some reason).

Probably because Google uses the Google Web Toolkit extensively, it’s a bit too difficult to dig into it and figure out what’s actually going on here. But what I think is happening is that keystrokes are being captured by Google globally, probably using a handler on the document object. These are then manually being added to the search box, even if they were typed into something else. I may be completely misrepresenting what is going on, because that’s frankly insane.

There are situations where you want a keystroke handler bound to the document object; global keystrokes can be used to implement keyboard shortcuts in a web application, for instance. Google needs this on the search results page to implement keyboard navigation for search results. (Arrow keys moving through the different matches, for instance.) However when you want to do something clever with text typed into a specific box, you should be careful to make your handler only respond to keystrokes for the input element. (jQuery has a way of binding a keystroke handler to one element, but having it only respond to keystrokes from child elements matching a selector, which may help here.) Alternatively, you may be able to use a change event on the input element instead.

I’m not really convinced that Google is doing what I’ve outlined above; for instance, there seems to be a keyboard handler on the input element as well as on the document object. But whether they are or not, you should definitely be careful if building something similar.

Accessibility Advent: please avoid being clever, particularly with form elements

(Throughout Advent I’m sharing some hints as to how web developers can make my life as a speech recognition user easier.)

If you want to be the opposite of helpful, why not try to be really clever and invent a completely new paradigm for a multistage process? Say, instead of using webpages, why not successively overlay different “form sheets” over the top of your actual webpage, so things like keyboard navigation through form elements have to go through everything in the original page before they get to the form you want someone to fill out?

If you do, you can enter the hallowed company of the Royal Museums Greenwich, where the only nice thing I can say about the experience is that pressing Escape closes the overlay.

There’s a nice “book now!” link (with correct alt text, meaning tools like Glee Box can find it), but instead of going to a new page it JavaScripts up an unsightly overlay. At this point it requires you to click a couple of links, and again Glee Box can manage this – but then you move to the second page:

Example of unhelpful for presentation

In Safari, Dragon has commands to move between only input fields ("next field” and “previous field"), which it does not have for Chrome (I believe because Safari provides a richer set of options via AppleScript). However this doesn’t help here, because behind the overlay there are still a load of input fields: a search box, and then separately a number of controls for filtering events by date. You have to advance through all of these before you get to the form controls you actually care about. (It’s even worse in Chrome, where you can only advance by tabbing; either you have to change the system setting so that tab advances only through form fields, or you just take even longer as you navigate through all the links on the page as well.)

This is basically a tabbing order issue. You could either set it explicitly, or you could just add your overlay at the beginning of the HTML document; you’re loading the content in it dynamically and then floating it over the top of everything else anyway, so I’m not convinced there’s a downside. Or, of course, you could just build separate pages. Unless your backend system is utterly messed up it should be pretty much as fast anyway, and you won’t have to bother creating or stealing a “loading” spinner – the browser has one already nicely built into it.

Accessibility Advent: show jump links on focus

(Throughout Advent I’m sharing some hints as to how web developers can make my life as a speech recognition user easier.)

Lots of sites provide a “jump to content” link designed for screen readers and the like; there often may be other jump links, to navigation, user tools and so on. Most people will then hide the links visually, typically by positioning them off the viewport, say using { position: absolute; left: -9999px; }.

A couple of sites go further, and show the links again when they are focused. Here that is on Mother Jones; hit tab a couple of times and watch the top right corner.

Here’s why I like it: although you can see the focused anchor target in the status bar in most browsers, it may not be visible depending on configuration, and more importantly you can style it to fit in with your site. Some people won’t look at the status bar at all, and most people are going to be more attentive to your content – within the viewport – than to the browser’s chrome.

The way Mother Jones uses can be implemented in CSS, but we can go further with a tiny bit of JavaScript and show the entire set of jump links when any one is focused, using a single line of jQuery or slightly more pure JavaScript. Then you can do more sophisticated styling, including pushing the entire site content down, which sounds like it goes against yesterday’s advice but which I’d be okay with because tabbing is a keyboard operation, so you’re not going to confuse someone in the middle of a mouse operation by doing this. It’s also obvious, unmissable – and that’s helpful.

Why does this matter? I tend to use Glee Box to navigate links by voice, but sometimes it doesn’t work, or there are too many links with the same anchor text, or I want to read a long article opening links as I go. Making it easy to skip focus past your header and navigation is just as useful for me as for the other potential audiences of those jump links.

Dragon Naturally Speaking on Windows allows you to speak voice links natively, so you don’t require a plug-in like Glee Box; however the same issues can arise, particularly many links on a page (Dragon will only respond to a certain number, to avoid taking too long and getting in the user’s way) and the desire to open links while reading long articles.