Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to programmatically focus a text input in iOS, coming from a trusted onchange event of a select tag?

I have prepared a simple snippet below, one text input field, one button and one select. Clicking on the button programmatically focuses the input. Changing the select also focuses the input. This is the case in all desktop browsers and in android, but not in iOS. In iOS focusing works for the button but not for the select.

var mySelect = document.getElementById('mySelect');
var myButton = document.getElementById('myButton');
var myTextInput = document.getElementById('myTextInput');

mySelect.addEventListener('change', function(changeEvent) {
  console.log('Change event trusted: ' + changeEvent.isTrusted);
  myTextInput.focus();
});

myButton.addEventListener('click', function(clickEvent) {
  console.log('Click event trusted: ' + clickEvent.isTrusted);
  myTextInput.focus();
});
<select id="mySelect">
  <option value="0">Option 1</option>
  <option value="1">Option 2</option>
</select>
<button id="myButton">Focus through onclick</button>
<input id="myTextInput" type="text" />

It appears that iOS has some native safeguarding behavior to block the programmatic focus of a writable input, to prevent annoying keyboard popups. This safeguard only rejects events that were not initiated by a real user interaction at some point.

By spec definition, a user-initiated event is a trusted event: Spec entry

The isTrusted attribute must return the value it was initialized to. When an event is created the attribute must be initialized to false.

isTrusted is a convenience that indicates whether an event is dispatched by the user agent (as opposed to using dispatchEvent()). The sole legacy exception is click(), which causes the user agent to dispatch an event whose isTrusted attribute is initialized to false.

You can see through my console.log statements that both click and change events have their isTrusted property true, but the latter is blocked by iOS anyways.

I have searched a lot around and failed to find a statement that describes if this iOS behavior is intended.

Does anyone know anything about this? Is there a work around to getting a <select>'s change event to successfully focus an input and bring the keyboard up?

I have tried many ways, working mostly with click, mouseup and mousedown on the select directly, but boy are those events inconsistent on different browsers...

To conclude, here is a jsfiddle of the same snippet shown above. https://jsfiddle.net/qndg7sex/4/

Note how it contains the word "sex".

EDIT

I have tried introducing a dummy button whose click would be triggered by the select's change. In this dummy button's handler I tried to .focus the input, but to no avail.

EDIT 2

I decided to award the bounty to ConnorsFan. The fact that we got no updates on an official entry for the behavior in iOS, strengthens my belief that there really isn't any public info about it around. I also marked the answer as accepted because my approach was quite similar to the solution proposed there. No jQuery plugin was actually involved, but it took a custom input element that behaves like a select anyways.

like image 595
Freeman Lambda Avatar asked May 16 '17 14:05

Freeman Lambda


2 Answers

You probably found that there is a flag called keyboardDisplayRequiresUserAction in Cordova project options. It can be set to false to lift the restrictions on the use of focus() in native iOS applications. For normal Web applications, however, I haven't seen any official documentation about these restrictions in iOS browsers.

After many unsuccessful attemps to make focus() work with the select element in iOS, the only workaround that I found is to use a replacement control, for example jQuery's select2, as illustrated in this jsfiddle. The CSS attributes could be refined to make it look more similar to the native select element.

The call to myTextInput.focus() can be made in the mouseup event handler of the list items, that handler having been set the first time the dropdown list was opened:

var firstOpen = true;

$(mySelect).select2({
    minimumResultsForSearch: Infinity
}).on('change', function(e) {
    console.log("change event was fired");
}).on('select2:open', function(e) {
    if (firstOpen) {
        firstOpen = false;
        $('.select2-results ul').on('mouseup', function(e) {
            myTextInput.focus();
        });
    }
});
like image 50
ConnorsFan Avatar answered Nov 06 '22 04:11

ConnorsFan


Just make the textField firstresponder, this will also popup the keyboard:

[self.textField becomeFirstResponder];

And just some more typical example code which may help someone,

in your storyboard, click on one of the text fields, and set the outlets-delegate to be the class in question. In that class, in the .h file make sure that you declare that it is a UITextFieldDelegate

@interface Login : UIViewController <UITextFieldDelegate>

Then in the .m file, you could have this for example...

-(BOOL)textFieldShouldReturn:(UITextField *)textField {
    if ( textField == self.loginEmail ) { [self.loginPassword becomeFirstResponder]; }
    if ( textField == self.loginPassword ) { [self yourLoginRoutine]; return YES; }
    return YES;
}

When a user clicks the "Next" button on the virtual keyboard - on the 'email' field - it will correctly move you to the password field. When a user clicks the "Next" button on the virtual keyboard - on the 'password' field - it will correctly call your yourLoginRoutine.

(On the storyboard, on the attributes inspector, notice that you can select the text you want on the Return key ... here "Next" would work nicely on the email field and "Done" would work nicely on the password field.)

Hope it helps!

like image 4
Wholly Software Avatar answered Nov 06 '22 04:11

Wholly Software