SAP / openui5

OpenUI5 lets you build enterprise-ready web applications, responsive to all devices, running on almost any browser of your choice.
http://openui5.org
Apache License 2.0
2.95k stars 1.23k forks source link

Can not set focus of input field in SimpleForm other than by setTimeout #3602

Closed s-v-o closed 1 year ago

s-v-o commented 1 year ago

OpenUI5 version: 1.106.0

Browser/version (+device/version): Vivaldi / Chromium

Any other tested browsers/devices(OK/FAIL): FAIL

I've tried to set the focus on an input field within a SimpleForm. All the documented ways (or more precisely the few stackoverflow answers and the proposed way in #2037) don't work and in most cases I end up to set the focus through using setTimeout. But this feels like a workaround and a little bit awkward. Also, I don't know how reliable this solution is. Especially in regard to network timings.

What I have tried:

I really don't understand why such a simple task like setting the initial focus after entering a view to a field is particularly so hard to achieve. I've lost hours in finding a way other than setTimeout, where it should be simply an attribute in the XML-View or a simple call to the API in a predefined controller hook (like onInit, but always executed when entering the view).

URL (minimal example if possible): https://plnkr.co/edit/vWYn7RL5FtXvZxVO

UserAbcd1234 commented 1 year ago

The SimpleForm internally creates a Form control with the corresponding settings. As the SimpleForm and the Form can have different Layout elements to use for rendering the corresponding Layout is created when the SimpleForm get's it settings. To prevent the loading of all possible layout modules only the required one is loaded. So the SimpleForm checks if the required modules are already loaded, if not it loads it. This could be asynchronously. The rendering of the Form and its content needs to wait until the modules are loaded, so this could lead to the case the content is not rendered synchronously.

However, if the modules are already loaded the content is rendered directly. In the most cases all required modules are somehow loaded by the application in some preload configuration, so normally it don't run into the asynchronous case. In your example this could be done by loading the module in the controllers define section.

As the default layout of the SimpleForrm is deprecated for a while I suggest to use the "ColumnLayout" as layout in the SimpleForm (defined in the view XML). (We cannot change the default for compatibility reasons.) Then in the Controller define section add "sap/ui/layout/form/ColumnLayout", this leads to the loading of the module before the rendering starts. Then the focus to the content control can be set directly.

You should also add the sap.ui.layout library to data-sap-ui-libs in index.htlm to make sure all necessary things of the layout library are loaded in time.

s-v-o commented 1 year ago

I've tried to change the example as you recommend, but it doesn't work either (the focus should be set to the second input field): https://plnkr.co/edit/o5msMxOJe6r5jxte?preview

Anyhow it is in my opinion bad DX, if the developer has to consider these things when he wants to set the focus to a field.

UserAbcd1234 commented 1 year ago

If you add "sap/ui/layout/form/ColumnLayout" to the define section of the Controller it will work, like I mentioned before. (I tested this.)

s-v-o commented 1 year ago

Yes, you are right: In the above posted plnkr it is missing (I'm sure I had tested it) But it didn't work without setTimeout (what is my point): https://plnkr.co/edit/rBi6w5TJxSJTCjNw

Maybe I understand you wrong? Maybe you could post a working example?

UserAbcd1234 commented 1 year ago

If you activate this.getView().addEventDelegate({ onAfterShow: ()=> { this.getView().byId("shorttextsimpleform").focus()
} }) on the init function it should work. The onPatternMatched function is executed before the rendering of the SimpleForm starts at all.

s-v-o commented 1 year ago

Nope, at least not in my example: https://plnkr.co/edit/hN1vf5V9P8tpukNW

UserAbcd1234 commented 1 year ago

If I run the sample it works, at least if you press the refresh icon in the preview window. Initially focus on the preview window seems not to work at all. Maybe because the focus is set outside the iframe.....

s-v-o commented 1 year ago

Ok, I had to try it outside of plnkr.

But I observed exactly that behavior especially when working in the launchpad shell: sometimes the focus is set, sometimes the focus switches i.e. to a launchpad button. This is for the user very annoying, especially when working with a mobile scanning device.

What I want is, that the focus is in the specified field (when first loading the view, when navigate back to the view, etc.).

Overall programming with sapui5 is fun (more so since I discovered typescript), but the focus handling is a hassle.

s-v-o commented 1 year ago
But I observed exactly that behavior especially when working in the launchpad shell: sometimes the focus is set, sometimes the focus switches i.e. to a launchpad button. This is for the user very annoying, especially when working with a mobile scanning device.

And it is exactly that what happens with the suggested change: I enter the app from launchpad. The focus is set to the wanted field and shortly after that the focus switches to the launchpad shell button (with the app name). The user is unable to enter something into the input field with the scanner.

ThomasChadzelek commented 1 year ago

Maybe this cheers you up. In Alan Cooper's book "About face" he quotes a Windows developer claiming that "focus is actually a contraction of two words, the second one being us". This topic is known to be frustrating since decades!

s-v-o commented 1 year ago

I give up.

First it seems, that the addEventDelegate variant works at least for subsequent views. But I discovered unwanted behavior there also. Some really weird one was that every second refresh the focus switches from the wanted field to the mentioned launchpad button.

I think I have to accept, that there is no really solution in the framework and I have to go with the setTimeout variant. This at least seems to work in most cases. So I go with the 80 % solution.

This topic is known to be frustrating since decades!

Yes, it is frustrating. But it shouldn't be. Nevertheless thank you for the kind words. Some point for good old ABAP: I never had a problem with SET CURSOR đŸ˜„

boghyon commented 1 year ago

@s-v-o What do you think about https://github.com/SAP/openui5/issues/2306#issuecomment-451465876? It's just an idea for now. But a declarative way to set the initial focus would definitely reduce some of the frustration. At least for application developers ;)

ThomasChadzelek commented 1 year ago

In the case above, it's the control with id="someControlIdInDetail". If the initialFocusTarget is not defined, then the control from the first target that has initialFocus defined. Any thoughts?

How would that play together with UI5 adaptation/p13n?

s-v-o commented 1 year ago

@boghyon Thank you for referencing the issue, I haven't noted it. By the way, thank you also for your well appreciated engagement on stackoverflow.

The manifest wouldn't be the place I would have expected. But I understand the reasoning and when it would work reliably I will be quite happy.

But a declarative way to set the initial focus would definitely reduce some of the frustration.

That would be really great.

UI5 adaptation/p13n

I would expect that p13n beats declaration or development. But is it possible for the user to set the initial focus?

I think a place/hook in the Controller, where the developer can be sure, that everything is loaded and rendered and which is called every time the view is shown, is really needed.

Also I would expect, that the framework would take care to place the focus where the developer said it should be. When there are other thinks which have to be executed before, the framework should postpone setting the focus or at least there should be a well documented and reliable way to set the focus.

flovogt commented 1 year ago

Hello @s-v-o , Thank you for sharing this finding. I've created an internal incident 2270167967. The status of the issue will be updated here in GitHub. Florian

stopcoder commented 1 year ago

Hi @s-v-o,

I would like to provide you with some of our findings about setting focus to a DOM element and the reason why we can't provide a mechanism to take care of setting the initial focus for a UI5 application. First of all, the initial focus is an important topic that must be done correctly in order to provide the end user with a proper starting point for the keyboard navigation. A DOM element needs to fulfill several prerequisites defined in the web browsers in order to be set with focus. Those prerequisites are well described in the jQuery documentation of the "focusable" pseudo selector. Without fulfilling those prerequisites, browsers simply ignore the call of the "focus" function on a DOM element. It could happen that the given control from the application for being set with the initial focus doesn't fulfill the prerequisites and UI5 framework doesn't know which element should be taken as an alternative for setting the focus. This leads to the result of having the focus set on the document.body which is bad for the keyboard navigation. Furthermore, the browsers on mobile devices have even more strict rules defined for setting focus to an input-like DOM element. The call to "focus" is accepted only when the call is done within the same call stack of an event handler, like a handler of the "touchstart" or "touchend" event. Some workaround is available but it's not guaranteed to work with the future version of the mobile browser. Due to the above reasons, we decided not to provide a general mechanism for setting the initial focus but providing some events to the application for informing that the DOM elements are ready for receiving the focus.

Some really weird one was that every second refresh the focus switches from the wanted field to the mentioned launchpad button.

Regarding the above issue, is it possible to provide us with an example? I can't reproduce the issue within FLP.

Best regards, Jiawei

s-v-o commented 1 year ago

Thank you for your extensive answer.

It could happen that the given control from the application for being set with the initial focus doesn't fulfill the prerequisites and UI5 framework doesn't know which element should be taken as an alternative for setting the focus.

But when I set the focus to a input control there should be a defined place where it is assured that it will work as expected. Currently this is not the case.

Regarding the above issue, is it possible to provide us with an example? I can't reproduce the issue within FLP.

Maybe this is a timing / caching issue. Currently I'm not very motivated to provide further examples.

For my problem I go now with the setTimeout variant because no other is working.

s-v-o commented 1 year ago

Some really weird one was that every second refresh the focus switches from the wanted field to the mentioned launchpad button

Currently I can observe this behaviour when using a SimpleForm with attachAfterShow (including a focus call and a setTimer call to focus and an import of ResponsiveGridLayout, see previous discussion), but the focus isn't set to the desired control when loading the app the first time on a mobile device with fiori client. After the first call the focus isn't set (also not on the launchpad button), when the app is started again, the focus is set.

boghyon commented 1 year ago

All the documented ways [...] don't work

@s-v-o I noticed setting the initial focus to the SimpleForm (in the documented way without setTimeout) works only if its layout is not sap.ui.layout.form.SimpleFormLayout.ResponsiveLayout which is, unfortunately, the default value for the layout property.

Solution: as suggested by @UserAbcd1234, setting another layout, e.g. layout="ColumnLayout", resolves the issue.

Here I extended my sample accordingly:

  1. Open https://embed.plnkr.co/wp6yes?show=view%2FHome.view.xml,controller%2FHome.controller.js,preview:%3Finitial-focus%3Dtarget2
  2. Notice how the SimpleForm receives the initial focus, even after navigating forward and back.
    Make sure also that your sap.m.App has autoFocus="false".
  3. To reproduce the issue: in the Home.view.xml definition, either remove layout="ColumnLayout" or set layout="ResponsiveLayout" to apply the deprecated form layout. Only then, setting the initial focus fails.

Could you try the same solution within your Fiori Client?
And with which SAPUI5 version does the Fiori Client run?

s-v-o commented 1 year ago

Thank you for your reply and your effort.

Also with ColumnLayout the behaviour is the same (tested in sandbox environment).

Launching from the sandbox launchpad

In the attached focus.zip I have set up a local environment.

If you want to try it:

boghyon commented 1 year ago

@s-v-o Thanks for the steps! I knew the issue in FLP is not reproducible with the "recent" SAPUI5 versions but not clearly since when. So I tried a bunch of SAPUI5 versions. To sum it up:

Test case

Given

Application code with a NavContainer and an afterShow handler for setting the initial focus as documented in the API reference.

[...] The afterShow event can be used to focus another element, only if autoFocus is set to false.

Sample code from the community: https://stackoverflow.com/a/48559689/5846045

Result

The FLP shell code is not part of this OpenUI5 repo. If possible, you could create a customer incident instead.

s-v-o commented 6 months ago

I find a hack that solves the issue for me.

Component.js init

this.saveSendFocusBackToShell = AccessKeysHandler?.sendFocusBackToShell;
if (this.saveSendFocusBackToShell) {
  AccessKeysHandler.sendFocusBackToShell = function () {};   
}

Component.js exit

if (this.saveSendFocusBackToShell) {
   AccessKeysHandler.sendFocusBackToShell = this.saveSendFocusBackToShell;
}