Saturday, November 27, 2004

Faster Data Entry In Forms

A site I use, Audioscrobbler.com, just introduced a moderation system to correct badly tagged artist names. It has a very simple design of yes/no/abstain as your own choices. But, it sucks! Why? Because of the interface.

People have a tendancy to bite off more than they can chew. I see people with 10, 20, 100, up to 1000 open moderations. If I want to vote on them, it sucks to have to use my mouse. Especially since it appears things are rendered exactly wrong enough to make a single press of my "down" arrow move the choices down to within 3 pixels of my click target. But I still have to move my mouse.

Something must be done! And it was. Inspired by Gmail's keyboard accelerators, and talking it over with some of the Audioscrobbler developers (who have too much work on their hands), I tried to push a SOAP speaking TUI written in C#. Alright, overkill, but it would have been a neat hack.

Instead, they wanted javascript keyboard enhancements. And that's what they got from me.

It's pretty simple. After blundering around a while reading documentation I discovered there was no Keyboard Events module in DOM level 2 Events. That's coming in DOM Level 3. Hmm. Well, if Gmail can do it, so can I!

Much googling, and where do we end up? Sometime back in the misty past of 2000. I know, I'm scared too. Now, we find on quirksmode.org some hints.

document.captureEvents( Event.KEYUP );
document.onkeyup = doSomething;

How simple!

function doSomething(e) {
var code;
if (!e) var e = window.event;
if (e.keyCode) code = e.keyCode;
else if (e.which) code = e.which;
var character = String.fromCharCode(code);

switch (character) {
case "Y":
vote(true);
break;
case "N":
vote(false);
break;
case "H":
vote(null);
break;
case "J":
move(1);
break;
case "K":
move(-1);
break;
default:
break;

}
}

That code explains itself. Move back or forwards, or vote true/false/null.

function vote(value) {
var options = rows.item(index).getElementsByTagName("input");
switch (value) {
case true:
options.item(0).checked="checked";
break;
case false:
options.item(1).checked="checked";
break;
case null:
options.item(2).checked="checked";
break;
default:

break;
}
move(1);
}

Vote, change the TR's relevant INPUT element's checked attribute to checked. Move to the next one.

function move(amount) {

if ((index + amount) <>= rows.length) { return; alert(index + "" + amount); }
else {
repaint(index);
index += amount;
highlight(index);
}
}

Move the index pointer thing, which keeps track of which one we are up to. Think of it like an array[index].


function repaint(n) {
try {
rows.item(n).className="row" + (n%2);
}
catch (e) { }
}


Repaint the GUI by swapping it's CSS classname. Use n%2 to get either "0" or "1" for any value of n. (Modulus, or as you may know it, remainder. 7/2 = 3 Remainder 1, (7 % 2) = 1). If you had 3 css classes, n % 3, etc etc etc.


function highlight(n) {
try {
rows.item(n).className="highlight";
}
catch (e) { }
}


Just to opposite! Highlight it.

Simple kiddies. See it all together now...

As always, Creative Commons pwns you. Or doesn't.

No comments: