How to Set Initial Focus in a View?
I have a Detail view in an SAPUI5 app which contains a single input field with ID "bestellmenge_tu"
. Whenever this view is called, the focus should be on that input field. Unfortunately, when setting the focus on the field in the controller's onInit
method, the focus will be set but some milliseconds later the UI5 takes it away and transfers it to the "navigation back" button of the detail view.
By putting a log.trace()
on the input field's blur
event, I found out that the focus is taken away by a method called sap.ui.define.NavContainer._afterTransitionCallback
which is called asynchronously (some window.setTimeout
s between trigger and execution). The function simply looks for the first focusable element in the view and brutally switches the focus on it.
My workaround was to redefine the method jQuery.fn.firstFocusableDomRef
which is used to find this "first focusable element":
// DIESE KANONE FUNKTIONIERT
jQuery.fn.firstFocusableDomRef = (function() {
var _default = jQuery.fn.firstFocusableDomRef;
return function() {
var bestellmenge_tu = document.querySelector("input[id$='bestellmenge_tu-inner']");
if (bestellmenge_tu &&
bestellmenge_tu.style.display !="none" &&
bestellmenge_tu.style.visibility != "hidden") return bestellmenge_tu;
else return _default.apply(this);
}
})();
But this could be a performance issue (querySelector
called during DOM transversal at any page load from there on), and it is too much coding for the desired effect.
Is there an easier method to achieve this?
I thought of something like
<mvc:View controllerName="zrt_dispo.view.Detail"...>
<Page id="detailPage" initialFocus="bestellmenge_tu"> <!-- ID of the element to carry the focus -->
</Page>
</mvc:view>
Solution 1:
Here is a working example: https://embed.plnkr.co/wp6yes/
<App autoFocus="false" xmlns="sap.m"> <!-- AND/OR -->
<f:FlexibleColumnLayout autoFocus="false" xmlns:f="sap.f" /> <!-- autoFocus in FCL available since 1.76 -->
{ // Controller of the target view:
onInit: function() {
this.attachAfterShow(this.onAfterShow);
},
attachAfterShow: function(onAfterShow) {
this._afterShowDelegate = { onAfterShow };
this.getView().addEventDelegate(this._afterShowDelegate, this);
},
onAfterShow: function() {
this.byId("thatControl").focus();
},
onExit: function() { // detach delegates
this.getView().removeEventDelegate(this._afterShowDelegate);
this._afterShowDelegate = null;
}
}
If you have a NavContainer (sap.m.App
or in sap.f.FlexibleColumnLayout
), its direct child, aka. NavContainerChild which is usually a View, can react to navigation related events. In our case, we should attach a handler to the event afterShow
according to the API reference:
The
afterShow
event can be used to focus another element, only ifautoFocus
is set tofalse
.[source]
The event handler is fired after the animation is finished. And most importantly:
This event is fired every time (in contrast to
onAfterRendering
) when the NavContainer has made this child control visible.
Since sap.ui.core.Control
extends sap.ui.core.Element
, every control can receive the focus via focus()
.
Note
- As mentioned in this post, the first focus on app launch from the Fiori Launchpad (FLP) is controlled by the launchpad and there is currently no API to change that behavior.
- In UI5 1.62 and below, calling
focus()
inonAfterShow
alone would still make the app set the focus on the first focusable element when the user navigates back, even withautoFocus="false"
(See this GitHub issue). Therefore, an additionalsetTimeout
with0
ms (orrequestAnimationFrame
) is needed in order to let the browser know that the element should be focused at the end of the call stack.
With commit:onAfterShow: function() { // Only if UI5 version < 1.63: setTimeout(() => this.byId("thatControl").focus()); },
6d46cf0
, the fix is available as of 1.63.
Solution 2:
The NavContainer
has a property autoFocus. App
is a descendant of NavContainer
so it has that property too.
The help (as linked above) states the following:
Determines whether the initial focus is set automatically on first rendering and after navigating to a new page. This is useful when on touch devices the keyboard pops out due to the focus being automatically set on an input field. If necessary the "afterShow" event can be used to focus another element.
Default value is true.