IAmTheRockstar

Yes. Yes I am.
Ubuntu One Engineering : Frontend Feedback for the User
April 5 2011

For the last six months, I've been working closely with Zac Bir on really polishing the web frontend for Ubuntu One. I thought I'd sit down and write a bit about what we're doing and how we're doing it.

I was tired of not having a very good way of reporting feedback and errors to the user. The error message doesn't have to be that complicated, and sometimes the feedback just needs to be enough to say "Hey, I saw your click, and I'm working on it, but it'll take an indeterminate amount of time." I didn't like Launchpad's spinner, because it forces heavy page reflows (and sometimes repaints!). I searched/experimented with this for a while before I came to a conclusion.

What you see above is the culmination of two ideas I'd been playing with for a while. Essentially, instead of having the spinner show up underneath what you clicked on (forcing the repaint/reflow), the spinner sits over the content. This is a problem in cases where we don't want to block the user while we do something, but in the case of the video, we do (we don't want them adding contacts while we're trying to find duplicates).

If there's a error, we have a nice error box that also doesn't reflow, sits "over" the contact (it's actually absolutely positioned), and reports the error back to us. I should also add that we kiped this look from Landscape. They've really by putting effort into UI sexiness.

Now, the UX porn is okay (it'll get better as we iterate). The best part (obviously in my opinion, but I wrote this so grain of salt and all that) is the API we're using to do all of this. It's entirely asynchronous and event driven.

For instance, here's what an io call would look like:

YUI().use('io', function(Y) {
    Y.Global.fire('one:busy');
    Y.io('/ajax_endpoint', {
        on: {
            success: function(id, response) {
                /* Do your thing here... */
                Y.Global.fire('one:idle');
            },
            failure: function(id, response) {
                /* Do failure things here */
                Y.Global.fire('one:idle');
                Y.Global.fire('one:error', {message: 'An error occurred. We suck.', error: response.responseText});
            }
        }
    });
});

Now, ideally we should be firing 'one:idle' in the 'complete' handler, except that complete executes after success and failure, so you can get some weird bugs that way. The end result is the same though: the user gets a friendly message, and we report the error to the server side to be dealt with in our error reports (this part is still getting ironed out, however).

All opinions expressed here constitute my personal opinion, and do not necessarily represent the opinion of any other organization or person, including, but not limited to, my fellow employees, my employer, its clients or their agents.