Column visibility menu tabbing outside

Column visibility menu tabbing outside

silkspinsilkspin Posts: 127Questions: 28Answers: 5

When using the ColVis menu, I'm trying to get the tabbing to cycle through the menu. After the last tabbed element it goes to the next element underneath the menu. This would be confusing for users with screen readers. I have tried it on this example.

https://datatables.net/extensions/responsive/examples/column-control/column-visibility.html

I'd like to keep tabbing inside the menu. I was having a similar problem with Bootstrap modals but fixed this with <div class="modal" tabindex="-1">. That traps the focus inside the modal until escaping or using a close button.

Another ColVis related question is could aria-labels be included on the toggled columns so visually impaired people know whether the column is shown or hidden? Is there a recommended way of achieving this? I wondered if maybe a different way of presenting this would work better. For example checkboxes.

Replies

  • allanallan Posts: 56,518Questions: 1Answers: 8,971 Site admin

    Hi,

    Thanks very much for this!

    div class="modal" tabindex="-1"

    Is that a Bootstrap thing? MDN says about negative numbers for tabindex:

    A negative value (usually tabindex="-1") means that the element is not reachable via sequential keyboard navigation, but could be focused with JavaScript or visually by clicking with the mouse

    It would be really handy if that is something we could just add to the dropdown container, but I've just tried it, and it doesn't keep the tab focus inside the container.

    Another ColVis related question is could aria-labels be included on the toggled columns so visually impaired people know whether the column is shown or hidden?

    Very nice idea. Would "Column {name} is currently visible/hidden. Toggle to hide/show." be suitable do you think? Quite a few language / i18n considerations there unfortunately.

    I wondered if maybe a different way of presenting this would work better. For example checkboxes.

    Based on the active class on the button you could use a ::before pseudo element to display a checkbox if you like.

    Allan

  • silkspinsilkspin Posts: 127Questions: 28Answers: 5
    edited November 2021

    Hi @allan. I initially added tabindex="0" to the modal title and some jQuery to automatically focus the title after a modal button click, but the focus still tabbed out. You are correct that the idea behind tabindex="-1" is to skip an element from the tab index, but I found that applying it to the top level of the modal allows for focusing inside, and traps it too. From https://webaim.org/techniques/keyboard/tabindex

    A tabindex="-1" value removes interactive elements from the default navigation flow. In most cases, this would not be desirable. If added to something that is not already interactive, tabindex="-1" allows that element to receive programmatic focus. This means focus can be set to it using focus() scripting. This may be useful for elements that should not be navigated to directly using the Tab key, but need to have keyboard focus set to them. Examples include a modal dialog window that should be focused when it is opened, or a form submission error message that should be immediately focused when an errant form is submitted.

    I also tried tabindex="-1" on the ColVis menu without success, but wasn't sure if I'd applied it correctly. It might still be possible, but maybe it needs structuring in a different way, but it seems to me it is a type of modal too.

    Regarding the state of the ColVis toggle buttons... I think your suggestion does sound good, but maybe there is a quick way to fix this. What I've done to other buttons, for example one that clears the search box and dropdown filters is...

    • On initialisation of DT I've added $(".dt-buttons .clear-filtering").prop("disabled", true); which when read by a screen reader says the button aria-label and then says "dimmed". I would assume an aria-label wouldn't be necessary because the column name would be read out first.
    • The above button is also initially styled as being disabled.
    • I have a function which detects whether the search box or any dropdown have a value and changes the style of the button to active.
    • The function also removes the "disabled" attribute.
    • Once the button is used to clear the search and filters, the style goes back to disabled and the attribute added again.

    These are the 2 conditionals I use in that function.

    // search or filter contains a value
    $(".dt-buttons .clear-filtering").removeClass("disable-button").prop("disabled", false);
    
    } else {
    
    // search or filter contains NO value
    $(".dt-buttons .clear-filtering").addClass("disable-button").prop("disabled", true);
    

    I think this might be feasible, but you are the expert so I'll let you decide! :)

  • silkspinsilkspin Posts: 127Questions: 28Answers: 5

    Hi @allan, I've been doing further work on this.

    I've found a way for screen readers to know the toggle state of the ColVis buttons. I can't use aria-labels on the ColVis individual buttons, because that overrides the button name, so what is needed is the title attribute which reads out after the button name. I've included a one time function when clicking the ColVis menu button. It initially adds the same title attribute to all buttons. Obviously this only works if all DT columns are shown after initialisation. A function would be needed to get the current active buttons if some columns are off by default.

    $(document).one("click", "button.dt-button.buttons-collection.buttons-colvis", function() {
        $(".dt-button-collection button").each(function() {
            $(this).attr("title", "toggle column off");
        });
    });
    

    Then another function that only works on the individual ColVis buttons checks whether the button has the active class (or not), and changes the title attribute accordingly. I've done some testing and it appears to work, but more testing would be wise just in case!

    $(document).on("click", "div.dt-button-collection > div > button", function() {
        if ($(this).hasClass("active")) {
            $(this).attr('title', 'toggle column off');
            console.log("false");
        } else {
            $(this).attr('title', 'toggle column on');
            console.log("true");
        }
    });
    

    You asked whether the tabindex="-1" was a Bootstrap thing. It turns out it is and there is an example under "Live Demo"… https://getbootstrap.com/docs/4.0/components/modal/

    I've found a partial fix for the ColVis menu not trapping the focus, which is confusing for the visually impaired. I can't take credit for this because I've lifted it from here… https://uxdesign.cc/how-to-trap-focus-inside-modal-to-make-it-ada-compliant-6a50f9a70700 and it's also available as a gist https://gist.github.com/myogeshchavan97/d50d42aa9205573b811587d57c2e58a6#file-trap_focus-js

    However, it works in Chrome and Safari, but the trap isn't working in Firefox, Edge and Opera, but that's better than nothing. All I've changed is document.querySelector('.dt-button-collection');.

    $(document).on("click", "button.dt-button.buttons-collection.buttons-colvis", function() {
    
        // add all the elements inside modal which you want to make focusable
        const focusableElements =
            'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
        const modal = document.querySelector('.dt-button-collection'); // select the modal by it's id
    
        const firstFocusableElement = modal.querySelectorAll(focusableElements)[0]; // get first element to be focused inside modal
        const focusableContent = modal.querySelectorAll(focusableElements);
        const lastFocusableElement = focusableContent[focusableContent.length - 1]; // get last element to be focused inside modal
    
    
        document.addEventListener('keydown', function(e) {
            let isTabPressed = e.key === 'Tab' || e.keyCode === 9;
    
            if (!isTabPressed) {
                return;
            }
    
            if (e.shiftKey) { // if shift key pressed for shift + tab combination
                if (document.activeElement === firstFocusableElement) {
                    lastFocusableElement.focus(); // add focus for the last focusable element
                    e.preventDefault();
                }
            } else { // if tab key is pressed
                if (document.activeElement === lastFocusableElement) { // if focused has reached to last focusable element then focus first focusable element after pressing tab
                    firstFocusableElement.focus(); // add focus for the first focusable element
                    e.preventDefault();
                }
            }
        });
        firstFocusableElement.focus();
    });
    
  • silkspinsilkspin Posts: 127Questions: 28Answers: 5

    UPDATE: After a few attempts at hard refreshes, the focus trap now works in Firefox, Edge and Opera, in addition to Chrome and Safari.

  • allanallan Posts: 56,518Questions: 1Answers: 8,971 Site admin

    Hi,

    Many thanks for your replies and insight!

    On initialisation of DT I've added $(".dt-buttons .clear-filtering").prop("disabled", true); which when read by a screen reader says the button aria-label and then says "dimmed".

    I'm a bit nervous about using the disabled state this way as a button can be active / inactive and enabled / disabled (any combination of the above). So using the disabled state to show inactive would probably lead to confusion somewhere down the line.

    Using the title attribute sounds good though. You can set the title attribute to an initial state using the titleAttr option for a button, and then update it using a function as you are. If that can be generalised, I can look at including that in the library.

    Regarding the trap - I will look at getting that integrated for the next release. I think that is the right thing to do.

    Allan

  • silkspinsilkspin Posts: 127Questions: 28Answers: 5

    Hi @allan

    I've not gone this deep with WCAG until now so a lot of it is new to me!

    My first suggestion at "dimmed" disabled buttons was before I needed to think of another way for the ColVis toggle buttons which can't be disabled and need to show a state. In my implementation disabled works because of the button's function. In my case the button is for clearing filters and the search box, and runs a check to see if it needs to be active or not. Originally I just styled the button as greyed out, and removed the cursor interaction. I was told for screen readers I should notify that it's disabled. Adding a dynamic title or aria-label would be just as good.

    I also had a similar button for clearing the ColVis, but now I've discovered the "Restore column visibility" feature I've styled the menu buttons and used dynamic toggle titles. I originally disabled the Restore visibility button until it was needed, but like you said that could cause confusion. It also breaks the focus trap and it jumps back out! Instead I changed it to a dynamic aria-label.

    The only minor problem I found with the trap focus code, was it does focus the 1st ColVis menu button firstFocusableElement.focus(); but doesn't highlight it with an outline showing it has focus. Tabbing down makes the outline appear. I've tried a few things but couldn't fix it. That could be more of a browser issue though.

  • allanallan Posts: 56,518Questions: 1Answers: 8,971 Site admin

    I think a dynamic aria-label sounds like it might be the way to go here. I'll have a think about what we can do there - we do it for column headers already (to state what the action will do to sorting), so it isn't unreasonable to also do that there. It needs to be modular though so it can work with other dropdown types, not just ColVis. Or more generally apply to all buttons.

    Really good point about disabled effecting the focus.

    Allan

  • silkspinsilkspin Posts: 127Questions: 28Answers: 5

    Hi @allan

    I'm resurrecting this post because my question is related to ColVis. I realised that when users navigate with keys and use the enter key, there are problems. This impacts the focus trap and the restore visibility button. Using Tab + Space on ColVis is fine and works the same as filter dropdowns which don't allow selection with the enter key. I've tried to disable it, and the console shows my code does recognise the enter key, but then the ColVis fires up straight after. Do you know of a way to prevent this? I've worked on variations of this code...

        $(document).on("click keypress keyup", "button.dt-button.buttons-collection.buttons-colvis", function(e) {
            if(e.which == 13) {
              e.preventDefault();
                console.log("enter key pressed");
                return;
            } else {
                console.log("other key pressed");
            }
        });
    
  • colincolin Posts: 14,381Questions: 1Answers: 2,446

    @silkspin We're happy to take a look, but as per the forum rules, please link to a test case - a test case that replicates the issue will ensure you'll get a quick and accurate response. Information on how to create a test case (if you aren't able to link to the page you are working on) is available here.

    Cheers,

    Colin

  • silkspinsilkspin Posts: 127Questions: 28Answers: 5

    @Colin This is regarding code @allan already knows about. I don't have a demo because he implemented his own testing of the focus trap code. The problem is whatever I do to try and stop "enter" opening the ColVis menu, DT ignores it probably due to the core files.

    You can see in the basic ColVis demo https://datatables.net/extensions/fixedcolumns/examples/initialisation/colvis.html how "space" and "enter" both open the menu. There must be a way of disabling "enter" because filter dropdowns in DT don't open on "enter". I just need the same behaviour. Otherwise the code Allan is incorporating into DT for the focus trap inside ColVis will not function as it should when people use the keyboard to navigate.

  • silkspinsilkspin Posts: 127Questions: 28Answers: 5

    @Colin @allan I've just knocked up a test case that was showing the same behaviour as my local dev version. I've added a distinctive focus so you know you are on it. This simplified version is now working after I made a minor change the focus trap code.

    I changed $(document).on("click" to $(document).on("keypress click" and now it seems to work with "space" and "enter".

    http://live.datatables.net/tumodebo/1/edit

    "space" was working with click but not "enter". If I just use "keypress" then the "space" doesn't work. So both triggers are needed.

    However, unfortunately trying to get this working locally isn't working. If I add "keypress" to the "click" I get the error Uncaught TypeError: Cannot read properties of null (reading 'querySelectorAll') which points to the line const firstFocusableElement = modal.querySelectorAll(focusableElements)[0]; // get first element to be focused inside modal.

    Can you offer any advice on this error, or back to me original question, can I disable the "enter' triggering the ColVis?

  • allanallan Posts: 56,518Questions: 1Answers: 8,971 Site admin

    The error message suggests to me that document.querySelector('.dt-button-collection'); isn't finding anything on your page for some reason. Without being able to see it, I'm not immediately sure what would cause that though.

    Can you offer any advice on this error, or back to me original question, can I disable the "enter' triggering the ColVis?

    There is no option built in to do that I'm afraid. However you could use:

    $(table.button(0).node()).off('keypress.dtb');
    

    to disable the listener we add.

    That said, with the return key, your demo seems to work really nicely - it is create to see that working so well.

    Allan

  • silkspinsilkspin Posts: 127Questions: 28Answers: 5

    Hi @allan I've been getting different results depending on which version of dataTables.buttons.min.js I use. I've tried all versions going back to 1.7.1 but at the moment I'm on 2.0.1 because it seems to function the best.

    v2.2.2 was causing problems with other buttons like the filter clear. For example I changed the styles to show as enabled/disabled and mouse click and space bar press would clear the filters and switch the button style. The enter key however just cleared the filters, but didn't switch style. This also looks down to the event type. Click is being used the same as in 2.0.1. I can't find any events that could be conflicting in my custom js. If I add keypress to the click then the clear function doesn't work. It seems to be a reoccurring theme when multiple events are detected.

    Regarding the other problem, in 2.0.1 I still get the Uncaught TypeError: Cannot read properties of null (reading 'querySelectorAll') error but only on a keypress or keydown event. It seems to me that keypress events run are too early and .dt-button-collection isn't ready, but it is for clicks.

    Currently I've got it working by just using "click" with 2.0.1. The only downside now is that if selected by enter key, the modal menu opens up, but it's not just receiving focus like with the space and mouse click, it's also being selected which means the first column is hidden. I've tried to stop that but so far I've failed to find a solution.

    Has the focus trap been published in Datatables? Maybe your implementation would mean I can ditch my version.

    Also, where exactly should I put the $(table.button(0).node()).off('keypress.dtb');? I haven't managed to place it in the correct place so far.

  • silkspinsilkspin Posts: 127Questions: 28Answers: 5

    @allan I've got a little further, although not solved it, but you will have a better idea of what is happening. It turns out the ColVis opened via click with the enter key triggers twice. That's why the first column shows as off. If I hold down enter on a button inside the menu, it flashes on and off and depending on when it's released it can go to the other state, but one click just toggles once and returns to what it was before. I've tried to prevent default and prevent propagation with no luck.

    It also turns out that the demo isn't trapping the focus if you follow these steps.
    1. open ColVis
    2. tab down menu and hit enter to turn off a column (all ok)
    3. tab down menu and hit space to turn off a column (focus escapes)

    http://live.datatables.net/tumodebo/1/edit

    My local dev copy does actually handle this fine, but like I mentioned I am using 2.0.1. If I switch back to 2.2.2 then mouse click and space bar click and tabbing works fine, trapping the focus. In the enter key click scenario the ColVis button opens the modal menu but keeps the focus around the ColVis button. Even entering the menu with tab doesn't trap it. That was why I thought disabling the enter key might be an idea because you can still use tab + space to navigate. Also 2.2.2 still has an issue with the clear filter button when I use that and I've not found a workaround. The double firing enter key isn't a problem in 2.2.2 though.

  • silkspinsilkspin Posts: 127Questions: 28Answers: 5

    After testing loads of combinations, I've found a simple fix. It doesn't seem to have impacted any other features as yet. It has fixed the double click that was being fired, and doesn't affect the clear filter button. I've edited dataTables.buttons.min.js 2.2.2 and changed both instances of…

                    .on( 'keypress.dtb', function (e) {
                        if ( e.keyCode === 13 ) {
                            e.preventDefault();
    

    with…

                    .on( 'click.dtb', function (e) {
                        if ( e.keyCode === 13 ) {
                            e.preventDefault();
    

    The focus trap can still escape in certain instances, especially when targeting the top or bottom buttons. I've added $(this).focus(); to the last clicked button which now seems to hold it in the menu.

  • colincolin Posts: 14,381Questions: 1Answers: 2,446

    Nice, thanks for reporting back,

    Colin

  • allanallan Posts: 56,518Questions: 1Answers: 8,971 Site admin

    Hi,

    Thanks for all your insights into this!

    I've just made a couple of commits for this:

    1. When you use the keyboard to activate a drop down, it will auto focus on the first button in the list - commit.
    2. Focus trap for the tab key - commit. This also handles "edge" cases such as the drop down being activated with the mouse but then using keyboard navigation for the drop down.

    Interested in your feedback if you give it a go! It will be in the nightly build shortly.

    Allan

  • silkspinsilkspin Posts: 127Questions: 28Answers: 5

    Thanks @allan. I've tested with mouse click and both space and enter selection. It all functions correctly and the focus doesn't escape.

    There is just a minor problem, BUT that is down to the customisation I've implemented. I have styled the restore visibility button depending on whether it's active or not. I've just updated the test I did and there aren't any states in the default version, so it isn't something you need to worry about http://live.datatables.net/tumodebo/4/edit

    It only affects my setup when using the enter key to select colvis buttons, and then it ignores the style on restore visibility button. It works fine for clicking and space. Shouldn't be too hard for me to fix that.

  • allanallan Posts: 56,518Questions: 1Answers: 8,971 Site admin

    Sounds good. Give me a shout if you'd like me to look at the styling. I did notice that we didn't have :focus styling on the "active" buttons with our default CSS. I added that in with the above changes.

    Allan

  • digitalchallengerdigitalchallenger Posts: 9Questions: 1Answers: 0

    Hello Allan,
    The columnvis is not a modal dialog, it is a expand/collapse element in my view. For state change of button use aria-pressed or use checkboxes instead of buttons.

    Coming to trapping focus in menu buttons, it is a terrible idea. We use tab & shift+tab to move between elements. In current implementation of buttons once the focus moves out of buttons the columnvis buttons collapse which is a design pattern accepted.

    if keyboard focus is trapped then this fails WCAG 2.1.2 keyboard trap.
    Check digitala11y.com header nav. The resources submenu will close as soon as keyboard focus moves out of the last element.

    If we want to trap the columnvis buttons as a dialog & need to trap the focus then we need to use role=dialog, aria-modal attributes on the divv that has the buttons.

    Ideally this approach is not great as it might cause multiple accessibility & usability problems. I prefer this button being a accordion with checkboxes or buttons with aria-pressed for state change. Do not use dynamic aria-labels.

  • allanallan Posts: 56,518Questions: 1Answers: 8,971 Site admin

    That's really interesting - thank you. I'd actually considered a modal rather than a expand/collapse since it needs to be activated (click or return key) before any of its sub-elements are placed into the DOM. Also, there is a "blocker" element in the background disallowing interaction with the rest of the page, which is why it felt right to trap focus in there until dismissed.

    Check digitala11y.com header nav. The resources submenu will close as soon as keyboard focus moves out of the last element.

    Thanks for the link - it took me a little while to figure out how to show the sub-menu with just the keyboard, but that's probably just my inexperience.

    I wonder if part of our ColVis dropdown issue is the aria-haspopup and aria-expanded properties? Should they be removed if I were to treat it as a modal (it has other display modes which make it visually much more like a modal in the centre of the screen - although I'm not sure if that has an impact)?

    Allan

  • digitalchallengerdigitalchallenger Posts: 9Questions: 1Answers: 0

    Hello Allan,
    since the menu is hiding the content on page I believe everyone is thinking this as a modal dialog. if we move to modal dialog route then we make sure keyboard focus is trapped inside the modal until user closes the modal with a close or escape key.

    I prefer the accordion button to expand/collapse elements & manage the visual aspect with CSS.

  • digitalchallengerdigitalchallenger Posts: 9Questions: 1Answers: 0

    Here is thee modal dialog example, yes we remove the aria-expanded when mmodal is being triggered. aria-haspopup is optional.
    https://www.w3.org/TR/wai-aria-practices/examples/dialog-modal/dialog.html

  • allanallan Posts: 56,518Questions: 1Answers: 8,971 Site admin

    Thanks!

    yes we remove the aria-expanded when mmodal is being triggered

    I don't actually see aria-expanded in that example at all. I've just been reading the spec about it and although it doesn't explicitly say that it be used for a modal, it does sound like it can be. aria-haspopup does explicitly say that it can be used for a dialog (which I take to also mean a modal).

    I've made a small change based on reading the spec to make the aria-haspopup be dialog. Although it is optional, based on the spec it seems like a good idea to include it?

    I've also added role=dialog and aria-modal=true to the popover element now.

    Allan

  • digitalchallengerdigitalchallenger Posts: 9Questions: 1Answers: 0

    ok, can i see a demo, will be able to test with screen reader & then give feedback.

  • colincolin Posts: 14,381Questions: 1Answers: 2,446

    Here you go - you can see the changes on the colvis button.

    Colin

Sign In or Register to comment.