Collect keyboard responses asynchronously in Javascript

Posted on Updated on


Some of our Mechanical Turk experiments are written in straight-up javascript, which gives you a lot of control and flexibility but at the expense of having to write some pretty basic functionality from scratch.  I recently was in a situation where I wanted to collect separate keyboard responses in different but possibly overlapping time windows: stimuli are coming in fast and on some of them, the subject needs to press the spacebar.  Rather than fix my design so that the response windows would never overlap, I decided to write a function that would collect a one-off keyboard response, asynchronously, meaning that other experiment control code can run behind it.

It does this using JQuery events and namespaces. The first step is to bind a keyup listener to document, which will call a provided callback function (and optionally filter by a list of accepted keys). This event handler is made unique by giving it a “random” namespace (actually just the millisecond clock time). The handler then removes itself when it either handles a response or times out, which is accomplished by creating a special “timeout” event (again, namespaced with the clock time to prevent all keyboard handlers from being removed).

The takeaway message is: JQuery is cool, and namespaces for even handlers are very, very cool. That’s what enables overlapping response time windows, multiple handlers, etc.

Here’s the code (also available as a forkable Gist):


/* collect a one-off keyboard response, using JQuery namespaces to allow multiple, overlapping responses
 *   fcn(e): function to be called with event on key press
 *   keys:   vector of allowed keys (as characters, uppercase for alphanumeric) (optional)
 *   to:     timeout for response, in ms (optinal)
 *   tofcn:  function to be called on timeout (optional)
 */

function collect_keyboard_resp(fcn, keys, to, tofcn) {
    // create unique namespace for this response
    var namespace = '._resp' + (new Date()).getTime();

    // bind keyup handler
    $(document).bind('keyup' + namespace, function(e) {
        // filter key-presses by list of allowed keys, if present
        if (!keys || keys.indexOf(String.fromCharCode(e.which)) != -1) {
            $(document).unbind(namespace);
            fcn(e);
            e.stopImmediatePropagation();
            return false;
        } else {
            return true;
        }
    });

    // if timeout function is specified, create event for calling it asynchronously
    if (typeof tofcn !== 'undefined') {
        $(document).bind('to' + namespace, function() {
                             $(document).unbind(namespace);
                             tofcn();
                         });
    }

    // if timeout is specified, set all handlers (keyup as well as timeout event) to expire
    if (typeof to !== 'undefined') {
        // timeout response after specified time and call function if it exists
        setTimeout(function(e) {
                       $(document).trigger('to' + namespace);
                       $(document).unbind(namespace);
                   }, to);
    }
}
Advertisements

Questions? Thoughts?

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

WordPress.com Logo

You are commenting using your WordPress.com 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 )

Google+ photo

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

Connecting to %s