Jemt / Fit.UI

Fit.UI is a JavaScript based UI framework built on Object Oriented principles
http://fitui.org
GNU Lesser General Public License v3.0
19 stars 7 forks source link

DropDown: Lose focus when scrolling if parent element has tabindex #106

Closed FlowIT-JIT closed 3 years ago

FlowIT-JIT commented 4 years ago

Consider the following example: http://jsfiddle.net/60fgdp1o/5/

image

Opening the dropdown cause control to lose focus to div#Container. This is obviously not the intended behaviour.

The bug only occur when using TreeView as the DropDown's picker control. ListView does not have this problem.

The difference is that ListView itself is focusable and fires OnFocus when it receives focus. DropDown takes advantage of this and "steals back" focus (see code below). But TreeView is not focusable, so scrolling this component cause the browser to assign focus to the nearest parent that is focusable.

image

Unfortunately the fix is not as simple as to just make TreeView focusable as well. Contrary to ListView, each item in a TreeView is focusable. A ListView relies on arrow up/down to select items which are just synthetically selected/highlighted. TreeView will have to select the first node when it receives focus, so the user doesn't have to press tab twice to select the first node. The challenge is making this work with backward tab navigation (SHIFT + TAB). We do not want the TreeView's container to be focused when pressing SHIFT+TAB while the top/first node is focused. We would expect focus to jump to the control before the TreeView. We would also have to detect when the SHIFT key is pressed since the first node should not be re-focused in this case of course which would prevent the user from tabbing backwards completely.

This problem only occur when some parent has tabindex set. Otherwise the DropDown control remains focused as one would expect. Therefore labeling "minor bug".

FlowIT-JIT commented 4 years ago

Possible solution:

image

Actual code

function makeFocusable()
{
    // Make sure control does not lose focus to a focusable parent when scrolling (http://jsfiddle.net/60fgdp1o/5/).
    // It would not be sufficient to apply this fix when used as a picker control since TreeView itself may be given
    // a fixed height which makes it scrollable if nodes does not fit within the given height.

    me.GetDomElement().tabIndex = 0;

    me.GetDomElement().onfocus = function()
    {
        if (Fit.Events.GetModifierKeys().Shift === false) // Shift+Tab: Navigate up/backwards
        {
            focusFirstNode();
        }
    }
    me.GetDomElement().onkeydown = function(e)
    {
        var ev = Fit.Events.GetEvent(e);

        if (ev.keyCode === 9 && Fit.Events.GetModifierKeys().Shift)
        {
            me.GetDomElement().tabIndex = -1;
        }
    }
    me.GetDomElement().onkeyup = function(e)
    {
        me.GetDomElement().tabIndex = 0;
    }
}

Test using debug.js

Fit.Events.OnReady(function () {
    //document.body.tabIndex = 0;

    var i = new Fit.Controls.Input();
    i.Render(document.body);

    var tv = new Fit.Controls.TreeView();
    tv.Selectable(true, true);
    tv.Width(100, "%");
    tv.Render(document.body);

    var dd = new Fit.Controls.DropDown("DropDown1");
    dd.Placeholder("Choose something");
    //dd.SetPicker(tv);
    dd.MultiSelectionMode(true);
    dd.Width(400);
    dd.DropDownMaxHeight(150);
    dd.InputEnabled(true);
    dd.Render(document.body);

    Fit.Array.ForEach(GetUsers(), function (user) {
        var n = new Fit.Controls.TreeViewNode(user.Name, user.Mail);
        tv.AddChild(n);
    });

    var lv = new Fit.Controls.ListView();
    Fit.Array.ForEach(GetUsers(), function (user) {
        lv.AddItem(user.Name, user.Mail);
    });
    lv.Render(document.body);

    if (Fit.Browser.IsMobile())
        dd.Width(100, "%");

    document.addEventListener("focus", function (e) {
        console.log("Element focused", e.target);
    }, true);
    document.addEventListener("blur", function (e) {
        console.log("Element blurred", e.target);
    }, true);
});

// ============================
// Get demo data
// ============================

window.GetUsers = function (picker) {
    var users =
        [
            { Name: "James Thomson", Mail: "james@server.com" },
            { Name: "Hans Törp", Mail: "hans@server.com" },
            { Name: "Ole Shortsom", Mail: "ole@server.com" },
            { Name: "Michael Burström", Mail: "michael@server.com" },
            { Name: "Ilda Longstum", Mail: "ilda@server.com" },
            { Name: "Martin Grom", Mail: "martin@server.com" },
            { Name: "Anders Handsom", Mail: "anders@server.com" },
            { Name: "Jacob Marking", Mail: "jacob@server.com" },
            { Name: "Jan Jacksson", Mail: "jan@server.com" },
            { Name: "Christian Fros", Mail: "christian@server.com" }
        ];

    return users;
}
FlowIT-JIT commented 3 years ago

Fixed. Notice that the solution implemented was not the suggested, as it turned out not to be necessary to manipulate/intercept the native implementation of tab navigation.