Drop-In Form Autofocus Script





3
Date Submitted Tue. Sep. 25th, 2007 10:50 AM
Revision 1 of 1
Helper chaos
Tags "form input" | "input focus" | autofocus | JavaScript
Comments 0 comments
This script performs reliable, cross-browser input autofocus that often can be used simply by dropping it in, with no modifications to the form or HTML body. It refuses to switch focus if it detects that the user has interacted with the form (avoiding a major source of annoyance with autofocus scripts) and correctly handles Firefox tabs opened "in the background", which most autofocus scripts fail on. Official home is on the Lost Souls MUD Grimoire.

// form_autofocus.js
//
// To use, place this file within your Web site, and include the following in
// the <head> section of your document, with $PATH changed to the URL path
// (Web address, not internal path name) where you have installed this file
// and square brackets changed to angle brackets:
//
//    [script type="text/javascript" src="/$PATH/form_autofocus.js"][/script]
//
// This script will automatically move the input focus (the user's cursor) to
// what looks like the most reasonable place for it.  The script's order of
// preference for where it will move focus is:
//
//    1) Empty text, textarea, and password inputs
//    2) Text, textarea, and password inputs with text in them
//    3) File inputs
//    4) Select inputs (single or multiple)
//    5) Radio button and checkbox inputs
//
// Input focus is moved using the onload event, and so will occur when page
// load is complete.  If it appears that the user has interacted with the
// page -- if any form inputs are not in their default state, including
// hidden inputs -- focus will not be changed.  This is to avoid the annoying
// case where you've already started typing into a form input, but a script
// interferes with what you're doing.
//
// The script will select the first input it encounters (in the order they
// are reported by the browser, which is generally the order they were
// defined in) out of the most preferred type of input it finds.
//
// You can alter the script's behavior in four ways.
//
//   1) You can set the variable document.focusOverride to a form input
//      object; this will make the script focus on that input instead of one
//      it selects.
//   2) You can include the class 'nofocus' on inputs; this will make the
//      skip them for purposes of selecting what to focus on.
//   3) You can include the class 'wantfocus' on inputs; the script will
//      preferentially select inputs with this class.  (If two inputs both
//      have 'wantfocus', the preference list above applies to them.)
//   4) You can include the class 'focusignore' on inputs.  This makes the
//      script ignore the input for purposes of determining whether the user
//      has interacted with the page.
//
// This script is known to work under IE 6, IE 7, Firefox 2, and Safari 3.
// This includes handling the case where a window is opened in a new tab that
// does not immediately receive window focus, which requires special treatment
// for some browsers.
//
// Official home: http://lostsouls.org/grimoire_form_autofocus.  Any updates
// will be found there.
//
// By Chaos of Lost Souls MUD, http://lostsouls.org
//
// 2007-09-24: version 1.0
//
// This code is released into the public domain.

// This information is provided AS IS and any express or implied warranties,
// including, but not limited to, the implied warranties of merchantability
// and fitness for a particular purpose are disclaimed. In no event shall the
// author or publisher be liable for any damages of any kind, however caused
// and on any theory of liability, arising in any way out of the use of this
// information, even if advised of the possibility of such damage.

if(window) {
    var formAutoFocus = function() {
        var focusTarget;
        var focusFlag;
        var focusDone = function() {
            focusFlag = true;
        }
        var attachFocus = function() {
            if(focusTarget.attachEvent) {
                focusTarget.attachEvent('onfocus', focusDone);
            } else {
                focusTarget.prevOnFocus = focusTarget.onfocus;
                if(focusTarget.prevOnFocus) {
                    var dualFocus = function() {
                        focusTarget.prevOnFocus();
                        focusDone();
                    }
                    focusTarget.onfocus = dualFocus;
                } else {
                    focusTarget.onfocus = focusDone;
                }
            }
        }
        var detachFocus = function() {
            if(focusTarget.attachEvent) {
                focusTarget.detachEvent('onfocus', focusDone);
            } else {
                focusTarget.onfocus = focusTarget.prevOnFocus;
                focusTarget.prevOnFocus = null;
            }
        }
        var checkFocus = function() {
            detachFocus();
            if(!focusFlag)
                window.setTimeout(formAutoFocus, 50);
        }
        var tryFocus = function(obj) {
            focusTarget = obj;
            focusFlag = false;
            attachFocus();
            try {
                focusTarget.focus();
            } catch(error) {
                return;
            }
            if(focusFlag) {
                detachFocus();
                return;
            }
            window.setTimeout(checkFocus, 10);
        }
        var tiers = new Array;
        if(document.focusOverride) {
            tryFocus(document.focusOverride);
        } else {
            for(var i = 0; i < document.forms.length; i++) {
                var form = document.forms[i];
                for(var j = 0; j < form.elements.length; j++) {
                    var elem = form.elements[j];
                    if(!elem.focus)
                        continue;
                    var tier;
                    switch(elem.type) {
                    case 'text'           :
                    case 'textarea'       :
                    case 'password'       :
                        if(elem.value != elem.defaultValue && !elem.className.match(/\bfocusignore\b/))
                            return;
                        tier = elem.value ? 1 : 0;
                        break;
                    case 'file'           :
                        if(elem.value != elem.defaultValue && !elem.className.match(/\bfocusignore\b/))
                            return;
                        tier = 2;
                        break;
                    case 'select-one'     :
                        if(!elem.className.match(/\bfocusignore\b/)) {
                            var anyDefault = false;
                            for(var k = 0; k < elem.options.length; k++) {
                                if(elem.options[k].defaultSelected) {
                                    anyDefault = true;
                                    break;
                                }
                            }
                            if(anyDefault) {
                                for(var k = 0; k < elem.options.length; k++)
                                    if(elem.options[k].selected != elem.options[k].defaultSelected)
                                        return;
                            } else {
                                if(elem.selectedIndex != 0)
                                    return;
                            }
                        }
                        tier = 3;
                        break;
                    case 'select-multiple':
                        if(!elem.className.match(/\bfocusignore\b/)) {
                            for(var k = 0; k < elem.options.length; k++) {
                                var option = elem.options[k];
                                if(option.selected != option.defaultSelected)
                                    return;
                            }
                        }
                        tier = 3;
                        break;
                    case 'radio'          :
                    case 'checkbox'       :
                        if(elem.selected != elem.defaultSelected && !elem.className.match(/\bfocusignore\b/))
                            return;
                        tier = 4;
                        break;
                    case 'hidden'         :
                        if(elem.value != elem.defaultValue && !elem.className.match(/\bfocusignore\b/))
                            return;
                        continue;
                    default               :
                        continue;
                    }
                    if(!tiers[0] && !elem.className.match(/\bnofocus\b/)) {
                        var wantfocus = elem.className.match(/\bwantfocus\b/);
                        if(!wantfocus)
                            tier += 5;
                        if(!tiers[tier])
                            tiers[tier] = elem;
                    }
                }
            }
            for(var ix = 0; ix < tiers.length; ix++) {
                if(tiers[ix]) {
                    tryFocus(tiers[ix]);
                    break;
                }
            }
        }
    }
    if(window.attachEvent) {
        window.attachEvent('onload', formAutoFocus);
    } else {
        if(window.onload) {
            var curronload = window.onload;
            var newonload = function() {
                curronload();
                formAutoFocus();
            };
            window.onload = newonload;
        } else {
            window.onload = formAutoFocus;
        }
    }
}
 

Comments

There are currently no comments for this snippet.

Voting

Votes Down