views:

112

answers:

2

Hello everyone,

I have created a form with malsup's Form Plugin wherein it submits on change of the inputs. I have set up my jQuery script to index drop down menus and visible inputs, and uses that index to determine whether keydown of tab should move focus to the next element or the first element, and likewise with shift+tab keydown. However, instead of moving focus to the first element from the last element on tab keydown like I would like it to, it moves focus to the second element. How can I change it to cycle focus to the actual first and last elements? Here is a live link to my form: http://www.presspound.org/calculator/ajax/sample.php. Thanks to anyone that tries to help. Here is my script:

$(document).ready(function() {
var options = {
    target: '#c_main',
    success: setFocus
};
$('#calculator').live('submit', function() {
    $(this).ajaxSubmit(options);
    return false;
});
$(this).focusin(function(event) {
    var shiftDown = false;
    $('input, select').each(function (i) {
        $(this).data('initial', $(this).val());
    });
    $('input, select').keyup(function(event) {
        if (event.keyCode==16) {
            shiftDown = false;
            $('#shiftCatch').val(shiftDown);
        }
    });
    $('input, select').keydown(function(event) {
        if (event.keyCode==16) {
            shiftDown = true;
            $('#shiftCatch').val(shiftDown);
        }
        if (event.keyCode==13) {
            $('#captured').val(event.target.id);
        } else if (event.keyCode==9 && shiftDown==false) {
            return $(event.target).each(function() {
                var fields = $(this).parents('form:eq(0),calculator').find('select, input:visible');
                var index = fields.index(this);
                var nextEl = fields.eq(index+1).attr('id');
                var firstEl = fields.eq(0).attr('id');
                var focusEl = '#'+firstEl;
                if (index>-1 && (index+1)<fields.length) {
                    $('#captured').val(nextEl);
                } else if(index+1>=fields.length) {
                    if ($(this).val() != $(this).data('initial')) {
                        $('#captured').val(firstEl);
                    } else {
                        event.preventDefault();
                        $(focusEl).focus();
                    }
                }
                return false;
            });
        } else     if (event.keyCode==9 && shiftDown==true) {
            return $(event.target).each(function() {
                var fields = $(this).parents('form:eq(0),calculator').find('select, input:visible');
                var index = fields.index(this);
                var prevEl = fields.eq(index-1).attr('id');
                var lastEl = fields.eq(fields.length-1).attr('id');
                var focusEl = '#'+lastEl;
                if (index<fields.length && (index-1)>-1) {
                    $('#captured').val(prevEl);
                } else if (index==0) {
                    if ($(this).val() != $(this).data('initial')) {
                        $('#captured').val(lastEl);
                    } else {
                        event.preventDefault();
                        $(focusEl).select();
                    }
                }
                return false;
            });
        }
    });
});

}); function setFocus() { with (document.calculator) var recap = document.getElementById(recaptured.value); if (recap!=null) { setTimeout(function() { if (recap.getAttribute('type')=='text') { recap.select(); } else { recap.focus(); } }, 100 ); } }

Edit #1: I made a few minor changes to the code, which has brought me a little closer to my intended functionality of the script. However, I only made one change to the code pertaining to the focus: I tried to to disable the tab keydown when pressed on the last element (and also the shift+tab keydown on the first element) in an attempt to force the focus on the element I want without skipping over it like it has been doing. This is the code I added:

$(this).one('keydown', function (event) {
    return !(event.keyCode==9 && shiftDown==true);
});

This kind of works. After the page loads, If the user presses tab on the last element without making a change to its value, the focus will be set to the second element. However, the second time the user presses tab on the last element without making a change to its value, and every subsequent time thereafter, the focus will be set to the first element, just as I would like it to.

Edit #2: I replaced the code in Edit #1, with code utilizing event.preventDefault(), which works better. While if a user does a shift+tab keydown when in the first element, the focus moves to the last element as it should. However, if the user continues to hold down the shift key and presses tab again, focus will be set back to the first element. And if the user continues to hold the shift key down still yet and hits tab, the focus will move back to the last element. The focus will shift back and forth between the first and last element until the user lifts the shift key. This problem does not occur when only pressing tab. Here is the new code snippet:

event.preventDefault();
$(focusEl).focus();
+1  A: 

You have a lot of code I didn't get full overview over, so I don't know if I missed some functionality you wanted integrated, but for the tabbing/shift-tabbing through form elements, this should do the work:

var elements = $("#container :input:visible");
var n = elements.length;
elements
.keydown(function(event){
    if (event.keyCode == 9) { //if tab
        var currentIndex = elements.index(this);
        var newIndex = event.shiftKey ? (currentIndex - 1) % n : (currentIndex + 1) % n;
        var el = elements.eq(newIndex);
        if (el.attr("type") == "text")
            elements.eq(newIndex).select();
        else
            elements.eq(newIndex).focus();
        event.preventDefault();
    }
});

elements will be the jQuery object containing all the input fields, in my example it's all the input fields inside the div #container Here's a demo: http://jsfiddle.net/rA3L9/

Simen Echholt
Thanks for your response, Simen. Your script works perfectly for cycling focus through form elements when the user doesn't change the values of any inputs. However, one of the key functions of my form is that it needs to submit on change and then reapply the focus to the correct element during the post-submit callback. I was accomplishing this by capturing the ID of the correct element in a text field, posting that value to another text field, and then calling a function to use that posted value to apply the focus to the correct element during the post-submit callback.
Daniel
Maybe it didn't answer the OP question exactly, but as a general purpose solution it's great! I was struggling with this weird behavior (focus on second control instead of first). Thanks, Simen!
Pawel Krakowiak
+1  A: 

Here is the solution, which I couldn't have reached it without Simen's help. Thanks again, Simen.

$(document).ready(function() {
    var options = {
        target: '#c_main',
        success: setFocus
    };
    $('#calculator').live('submit', function() {
        $(this).ajaxSubmit(options);
        return false;
    });
    $(this).focusin(function(event) {
        $('#calculator :input:visible').each(function (i) {
            $(this).data('initial', $(this).val());
        });
        return $(event.target).each(function() {
            $('#c_main :input:visible').live(($.browser.opera ? 'keypress' : 'keydown'), function(event){
                var elements = $("#calculator :input:visible");
                var n = elements.length;
                var currentIndex = elements.index(this);
                if (event.keyCode == 13) { //if enter
                    var focusElement = elements.eq(currentIndex).attr('id');
                    $('#captured').val(focusElement);
                } else if (event.keyCode == 9) { //if tab
                    var newIndex = event.shiftKey ? (currentIndex - 1) % n : (currentIndex + 1) % n;
                    var el = elements.eq(newIndex);
                    var focusElement = el.attr('id');
                    if ($(this).val() != $(this).data('initial')) {
                        $('#captured').val(focusElement);
                    } else if ((currentIndex==0 && event.shiftKey) || (currentIndex==n-1 && !event.shiftKey)) { 
                        event.preventDefault();
                        if (el.attr('type')=='text') {
                            $.browser.msie ? "" : $(window).scrollTop(5000);
                            el.select().delay(800);
                        } else {
                            $.browser.msie ? "" : $(window).scrollTop(-5000);
                            el.focus().delay(800);
                        }
                    } else if (el.is('select')) {
                        event.preventDefault();
                        if (el.attr('type')=='text') {
                            el.select();
                        } else {
                            el.focus();
                        }
                    }
                }
            });
        });
    });
});

function setFocus() {
    with (document.calculator)
    var recap = document.getElementById(recaptured.value);
    if (recap!=null) {
        setTimeout(function() {
            if (recap.getAttribute('type')=='text') {
                recap.select();
            } else {
                recap.focus();
            }
        }, 1     );
    }
}

I put my files available to download in my live link: http://www.presspound.org/calculator/ajax/sample.php

Daniel