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.
- Remove the
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.
onOpen()
method, rather than waiting for the user to press TAB. This also means we can drop the dependency ondojo/keys
.