2

I don't know if anyone else has encountered this, but I recently discovered that ever since v2.11 of Web AppBuilder, the TAB key navigation has broken in my in-panel 2D widget. (Modal and 3D are fine.)

I discovered there's a new file called client/stemapp/jimu.js/accessibleUtils.js which attempts to keep focus within the widget. The idea seems to be that if you press TAB on the last focusable node within the widget, it will "wrap around" to the first focusable node in the widget. Similarly, pressing SHIFT-TAB on the first focusable node would move the focus to the last focusable node.

In my case, the problem seems to be that when WAB tries to discover the first and last focusable nodes, my widget does not yet contain any. This results in the widget container becoming focusable and being the first and last focusable nodes. The result is when I press TAB inside a text box that I added later, the focus shifts to the frame of the widget and no amount of TAB or SHIFT-TAB will make a difference.

Note that there was no mention of this breaking change in the release notes.

Also, it appears that widgets now need to keep track of their first/last focusable nodes in order to continue to support this behaviour. I see many widgets call initFirstFocusNode() and initLastFocusNode() in order to keep up with the dynamic adding/removing of controls.

Is it expected that custom widget developers need to do this same housekeeping? This could be very complicated in our case and I'm reluctant to add so much effort in order to keep TAB working.

Is there some kind of way to opt out of this? It seems the default behaviour should be to add no classes/event handlers rather than making the widget's dom node focusable and setting its role to document.

In the short term, I have added a workaround to eliminate this behaviour from my widget. If you are similarly affected, you could do this too.

// Your `define` statement will need to include
// "dojo/_base/html" and "dojo/keys", which I refer to
// as `html` and `keys` respectively.

startup: function() {
    this.inherited(arguments);

    // (Your widget's startup code here.)

    if (this.inPanel) {
        var _this = this;
        var tabEventListener = function(event) {
            if (event.keyCode === keys.TAB) {
                if (_this.domNode.firstNodeEvent) {
                    _this.domNode.firstNodeEvent.remove();
                    delete _this.domNode.firstNodeEvent;
                    html.removeClass(_this.domNode, "firstFocusNode");
                }
                if (_this.domNode.lastNodeEvent) {
                    _this.domNode.lastNodeEvent.remove();
                    delete _this.domNode.lastNodeEvent;
                    html.removeClass(_this.domNode, "lastFocusNode");
                }
                _this.domNode.removeAttribute("tabindex");
                _this.domNode.removeAttribute("role");
                // The listener now removes itself. Our work here is done.
                _this.domNode.removeEventListener("keydown", tabEventListener);
            }
        };
        this.domNode.addEventListener("keydown", tabEventListener);
    }
}
  • The above code adds a keydown event handler on the widget's domNode before WAB's event handler is added. This means mine will be executed first.
  • When TAB is pressed (regardless of SHIFT) we do the following:
    • Remove the keydown event handlers from the widget's DOM node for it being the first and last focusable node.
    • Remove the related css classes.
    • Remove the tabindex attribute from the widget's DOM node to prevent being able to tab onto it.
    • Remove the role attribute from the widget's DOM node. (It does have controls now, so it doesn't need that role.)
    • My event handler then removes itself as its work is done.

The end result is I can now TAB and SHIFT-TAB through my widget again. I don't get the wrap-around focus but then I also don't get focus stuck on a div, so I consider it a win.

3
  • Ended up moving my fix to the onOpen() method, rather than waiting for the user to press TAB. This also means we can drop the dependency on dojo/keys.
    – Ken Lyon
    Nov 25, 2020 at 0:12
  • tried to ask in the esri forum (tinyurl.com/yky5vr3t) without success, your workaround looks working also in my case :) thanks!
    – D_Guidi
    Mar 12, 2021 at 14:41
  • ended up handling manually in the onOpen method, that is way powerful because enable the ciclying of the components so the focus is retained in the custom widget (solution in the esri link above). Hope it helps to anyone out there!
    – D_Guidi
    Mar 12, 2021 at 15:14

1 Answer 1

0

Thanks for posting the above - Here is a self contained generic fix based on your findings - just replace or add the onOpen function in the widget.js of every affected control with this:

onOpen: function () {
    console.log('CustomWidget::onOpen');
    //fix for tabs being broken in wab (v2.11+) - place in onOpen in each affected widget - Ambrose 2022
    _this = this;
    require(['dojo/dom', 'jimu/utils'], function (dom, utils) {
        var focusableNodes = utils.getFocusNodesInDom(_this.domNode);
        utils.initFirstFocusNode(_this.domNode, focusableNodes[0]);
        utils.initLastFocusNode(_this.domNode, focusableNodes[focusableNodes.length - 1]);
    });
},
1
  • Thanks for the suggestion. I think in my case I might need to call utils.initFirstFocusNode() and utils.initLastFocusNode() whenever I change the content of the panel. I'm not sure if it'd be worth it.
    – Ken Lyon
    Mar 7, 2022 at 19:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.