90

I have a simple input field like this.

<div class="search">
   <input type="text" value="y u no work"/>
</div>​

And I'm trying to focus() it inside a function. So inside of a random function (doesn't matter what function it is) I have this line …

$('.search').find('input').focus();

This works just fine on every Desktop whatsoever.

However it doesn't work on my iPhone. The field is not getting focused and the keyboard is not shown on my iPhone.

For testing purposes and to show you guys the problem I did a quick sample:

$('#some-test-element').click(function() {
  $('.search').find('input').focus(); // works well on my iPhone - Keyboard slides in
});

setTimeout(function() {
  //alert('test'); //works
  $('.search').find('input').focus(); // doesn't work on my iPhone - works on Desktop
}, 5000);​

Any idea why the focus() wouldn't work with the timeout function on my iPhone.

To see the live example, test this fiddle on your iPhone. http://jsfiddle.net/Hc4sT/

Update:

I created the exact same case as I'm currently facing in my current project.

I have a select-box that should — when "changed" — set the focus to the input field and slide-in the kexboard on the iphone or other mobile devices. I found out that the focus() is set correctly but the keyboard doesn't show up. I need the keyboard to show up.

Cœur
  • 37,241
  • 25
  • 195
  • 267
matt
  • 42,713
  • 103
  • 264
  • 397
  • maybe it _is_ focused, but the keyboard just doesn't show up (that's the case on my Nokia X6). – 11684 Aug 30 '12 at 20:12
  • 1
    works for me. jsfiddle you pasted just focused the box and opens a keyboard on my iphone. which iOS are you using? I've checked with 5.1. – Krizz Aug 30 '12 at 20:18
  • Ok, you're right as it seems. I'm on iOS 5.1.1. The focus() seems to work as I know set `input:focus { background:green; }` via css on the input. However the keyboard doesn't slide-in for me. See this work case I'm currently working on. http://cl.ly/1d210x1W3Y3W Test this on your iPhone. Any chance to slide in the keyboard when choosing "Search" in the select-box? – matt Aug 31 '12 at 06:48
  • @Krizz Are you sure it opens the Keyboard on your iPhone? I've read tons of stuff now and everybody says it's not even possible to show the keyboard programatically. – matt Aug 31 '12 at 07:55
  • Could you post the code for the update. Downloading a zip and somehow getting it to a position where I can test on my phone is a bit of a pain ;-) – Alex Aug 31 '12 at 12:09
  • @Alex Sure, check this … http://bit.ly/ONzR1w … I tried and tried and tried, but can't seem to slide-up the keyboard. Maybe you can find some way or trick to do so. I also tried to `trigger()` a click or "tap" on another element to set the focus - but no chance. Again, the focus is set, but the keyboard is not sliding up. – matt Aug 31 '12 at 12:50
  • 1
    I am trying alot of things. I tried triggerHandler(), focusin(), trigger("focus"). I even tried to blur it first and then re-add the focus. I think it might be a bug in webkit. You might be out of luck with this one...But I am going to try a couple more things... – Alex Aug 31 '12 at 13:17
  • I've read it's more like a feature not a bug in mobile safari. I didn't really get the reason why but I've stumbled upon quite a few posts regarding this. – matt Aug 31 '12 at 13:26
  • I found this, and a couple other articles regarding this matter. I think what you are trying to do is currently impossible. https://groups.google.com/forum/?fromgroups=#!topic/jqtouch/-uhJqVsqX_M – Alex Aug 31 '12 at 13:56
  • Do you happen to know which ASCII value the "Done" button equates to? Does is it act as a 13? (similar to the Enter key)? Trying to map a function to when you press the "Done" button to see if that does anything... – Alex Aug 31 '12 at 14:05
  • I tried $(document).keypress(function(e) { alert(); }); But the alert didn't fire for the "Done" button. It must not treat it as a keypress. – Alex Aug 31 '12 at 14:18
  • 1
    I am giving up. I tried everything. It's just not something you can do in the iOS Safari Web browser. Hopefully Apple makes a change to allow this because I certainly don't consider this a "feature"! haha I don't know why you can so easily blur() the field but can't set focus to it. Doesn't make sense to me. – Alex Aug 31 '12 at 15:09
  • Thank's for trying. I already gave up too. – matt Aug 31 '12 at 15:16
  • Do realize that you CAN set focus to the field and bring up the keyboard by clicking on another element on the page. That is easy. But for some reason, the jump from the drop down select wheel to the keyboard seems to be the culprit. It's like the iPhone doesn't allow back to back input screens without the user intervening first... – Alex Aug 31 '12 at 16:07
  • FWIW, I'm using the WP7 and I couldn't bring up the keyboard unless the input was visible; I didn't necessarily have to tap it. When the input was hidden via display:none, I couldn't bring up the keyboard. – SnakeWasTheNameTheyGaveMe Dec 08 '12 at 22:27
  • duplicate: http://stackoverflow.com/questions/6287478/mobile-safari-autofocus-text-field – uri.lazar Mar 14 '13 at 12:15

9 Answers9

86

Actually, guys, there is a way. I struggled mightily to figure this out for [LINK REMOVED] (try it on an iPhone or iPad).

Basically, Safari on touchscreen devices is stingy when it comes to focus()ing textboxes. Even some desktop browsers do better if you do click().focus(). But the designers of Safari on touchscreen devices realized it's annoying to users when the keyboard keeps coming up, so they made the focus appear only on the following conditions:

1) The user clicked somewhere and focus() was called while executing the click event. If you are doing an AJAX call, then you must do it synchronously, such as with the deprecated (but still available) $.ajax({async:false}) option in jQuery.

2) Furthermore -- and this one kept me busy for a while -- focus() still doesn't seem to work if some other textbox is focused at the time. I had a "Go" button which did the AJAX, so I tried blurring the textbox on the touchstart event of the Go button, but that just made the keyboard disappear and moved the viewport before I had a chance to complete the click on the Go button. Finally I tried blurring the textbox on the touchend event of the Go button, and this worked like a charm!

When you put #1 and #2 together, you get a magical result that will set your login forms apart from all the crappy web login forms, by placing the focus in your password fields, and make them feel more native. Enjoy! :)

Peter Brown
  • 50,956
  • 18
  • 113
  • 146
Gregory Magarshak
  • 1,883
  • 2
  • 25
  • 35
  • regarding #1, how do you do it synchronously from a keyboard event? – Michael Nov 30 '13 at 01:02
  • 2
    This sounds very intelligent, but I'm having a hard time "putting #1 and #2 together". In my app, using jQueryMobile, when a user clicks on a list item in page A, a new page B opens which has just one visible input. I want the keyboard to open automatically for that input. Do you mean to say I must put the .focus() call somewhere in the click handler on page A? – Wytze Feb 08 '14 at 17:06
  • I don't know if this is still working, is it? I clicked the login button and the keyboard flashed up and then slid down. Clicking the login button a second time didn't even produce the keyboard at all any longer. – Phillip Senn May 05 '14 at 16:06
  • 1
    Thank you for saving my time. That was so unobvious to find. – vogdb Dec 05 '14 at 12:13
  • @GregoryMagarshak I am trying to get this to work in an angular application in which a link clears the input's text, and want the focus to remain in that field. I was able to get the keyboard to flash using touchstart as you mentioned, but using touchend the keyboard still disappears! – ErikAGriffin Jul 10 '15 at 10:09
  • 4
    I was able to get it! Instead of blurring and focusing the element, I simply added `event.preventDefault()` on `touchstart`. Note that this prevents the ng-click from executing, so I had to add my clear text logic inside the event handler and `$scope.$apply()` – ErikAGriffin Jul 10 '15 at 10:38
  • 2
    I found that just running `input.focus()` from a button's click handler, worked. Safari on iPhone 5 and iPad. If I had the `focus()` in a setTimeout, that didn't work. So maybe that's what "synchronously" means, @Michael. – Dan Dascalescu Jul 13 '15 at 11:08
  • 2
    Thank you for posting an actual solution. Blurring previous element on touchend + setting focus on click event does work in iOS10 still. – Kirill E. Jan 17 '17 at 17:15
  • I'm glad this answer is helping so many people after all these years. It's one I'm the most proud of on SO :) – Gregory Magarshak Jan 17 '17 at 17:27
  • @GregoryMagarshak you can also do it async with a simple trick. In the original click handler you have to create fake input, append it to doc body and focus() it. This will bring the keyboard up. After that, when the proper input is visible(e.g. after Ajax call) just use focus() again on that input. When you have the keyboard up you can travel with focus() to any input you want :D – WunderBart Jun 23 '17 at 12:11
  • Can you elaborate on your last sentence? Have you tried in iOS and Android? – Gregory Magarshak Jun 23 '17 at 22:46
  • 1
    Hey @DanDascalescu - thanks for the solution! I can confirm it works on iOS :) – Vladyn Sep 11 '20 at 13:41
46

A native javascript implementation of WunderBart's answer.

function onClick() {

  // create invisible dummy input to receive the focus first
  const fakeInput = document.createElement('input')
  fakeInput.setAttribute('type', 'text')
  fakeInput.style.position = 'absolute'
  fakeInput.style.opacity = 0
  fakeInput.style.height = 0
  fakeInput.style.fontSize = '16px' // disable auto zoom

  // you may need to append to another element depending on the browser's auto 
  // zoom/scroll behavior
  document.body.prepend(fakeInput)

  // focus so that subsequent async focus will work
  fakeInput.focus()

  setTimeout(() => {

    // now we can focus on the target input
    targetInput.focus()

    // cleanup
    fakeInput.remove()
    
  }, 1000)

}

Other References: Disable Auto Zoom in Input "Text" tag - Safari on iPhone

Raine Revere
  • 30,985
  • 5
  • 40
  • 52
  • 8
    This worked for me. If you want to prevent showing keyboard on the fakeInput focus, add readonly="true" to it. – Branislav Kuliha Jun 04 '19 at 12:36
  • Only solution that works. requestAnimationFrame is sufficient for me and eliminates the delay. – Thomas Jan 20 '20 at 20:52
  • 3
    This answer should be shared across other similar issues that deals with making setTimeout + focus on Safari browsers. It is the one thing that really works! – wentjun Apr 01 '20 at 03:22
  • 1
    Thank you! This is the only solution that worked for me. – Offek Oct 25 '20 at 09:49
  • 1
    works, +1. But it seems that timeout matters, with 0 or small time lapse like 250ms it doesn't work. Apple folks should be ashamed of themselves because they made trivial task into something enormous and opaque. – curveball Apr 12 '21 at 16:30
  • 1
    Keyboard open works well with this solution but it breaks offering OTP code to autofill in. Above keyborad should be text after received message to autofill code by click it. With this solution I have to manually close keyboard and open again by input clicking and text over keyboard is shown. Stupid Apple, I hate this neverending fixing of fix. – mikep Jul 26 '21 at 13:59
  • December 2021, iOS 15: `opacity` must be at least `0.1001` or it won't work. – Aidin Dec 05 '21 at 22:00
  • Only solution that works, incredibly hacky... but it works. – Craig Howell Feb 25 '22 at 18:09
  • It doesn’t work for me in React Native Web. – gricn Aug 15 '22 at 02:18
  • You can make this solution less hacky (i.e. get rid of the race condition) by using a Promise that you resolve as soon as the input is ready for focus. – Paul Statezny Feb 16 '23 at 15:41
  • If the page is tall, the website may scroll all the way up to the top, to the fake input, then back down to the real input. To avoid that, use position `fixed` instead. That will position the fake input on the current scroll position. – João Souza Apr 27 '23 at 21:37
7

I faced the same issue recently. I found a solution that apparently works for all devices. You can't do async focus programmatically but you can switch focus to your target input when some other input is already focused. So what you need to do is create, hide, append to DOM & focus a fake input on trigger event and, when the async action completes, just call focus again on the target input. Here's an example snippet - run it on your mobile.

edit:

Here's a fiddle with the same code. Apparently you can't run attached snippets on mobiles (or I'm doing something wrong).

var $triggerCheckbox = $("#trigger-checkbox");
var $targetInput = $("#target-input");

// Create fake & invisible input
var $fakeInput = $("<input type='text' />")
  .css({
    position: "absolute",
    width: $targetInput.outerWidth(), // zoom properly (iOS)
    height: 0, // hide cursor (font-size: 0 will zoom to quarks level) (iOS)
    opacity: 0, // make input transparent :]
  });

var delay = 2000; // That's crazy long, but good as an example

$triggerCheckbox.on("change", function(event) {
  // Disable input when unchecking trigger checkbox (presentational purpose)
  if (!event.target.checked) {
    return $targetInput
      .attr("disabled", true)
      .attr("placeholder", "I'm disabled");
  }

  // Prepend to target input container and focus fake input
  $fakeInput.prependTo("#container").focus();

  // Update placeholder (presentational purpose)
  $targetInput.attr("placeholder", "Wait for it...");

  // setTimeout, fetch or any async action will work
  setTimeout(function() {

    // Shift focus to target input
    $targetInput
      .attr("disabled", false)
      .attr("placeholder", "I'm alive!")
      .focus();

    // Remove fake input - no need to keep it in DOM
    $fakeInput.remove();
  }, delay);
});
label {
  display: block;
  margin-top: 20px;
}

input {
  box-sizing: border-box;
  font-size: inherit;
}

#container {
  position: relative;
}

#target-input {
  width: 250px;
  padding: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div id="container">
  <input type="text" id="target-input" placeholder="I'm disabled" />

  <label>
    <input type="checkbox" id="trigger-checkbox" />
    focus with setTimetout
   </label>
</div>
WunderBart
  • 1,355
  • 1
  • 9
  • 9
5

This solution works well, I tested on my phone:

document.body.ontouchend = function() { document.querySelector('[name="name"]').focus(); };

enjoy

Henk van Boeijen
  • 7,357
  • 6
  • 32
  • 42
istvan makary
  • 51
  • 1
  • 1
  • This ALMOST works. But doesnt scale to two datefields, and kind of makes the page a bit glitchy – Shayne Aug 22 '17 at 09:21
  • Thank you! Tried all sorts of stuff and this finally did it for me where for some reason document.getElementById did not – daamsie Sep 06 '19 at 04:00
3

I have a search form with an icon that clears the text when clicked. However, the problem (on mobile & tablets) was that the keyboard would collapse/hide, as the click event removed focus was removed from the input.

text search input with close icon

Goal: after clearing the search form (clicking/tapping on x-icon) keep the keyboard visible!

To accomplish this, apply stopPropagation() on the event like so:

function clear ($event) {
    $event.preventDefault();
    $event.stopPropagation();
    self.query = '';
    $timeout(function () {
        document.getElementById('sidebar-search').focus();
    }, 1);
}

And the HTML form:

<form ng-controller="SearchController as search"
    ng-submit="search.submit($event)">
        <input type="search" id="sidebar-search" 
            ng-model="search.query">
                <span class="glyphicon glyphicon-remove-circle"
                    ng-click="search.clear($event)">
                </span>
</form>
DeBraid
  • 8,751
  • 5
  • 32
  • 43
2

I managed to make it work with the following code:

event.preventDefault();
timeout(function () {
    $inputToFocus.focus();
}, 500);

I'm using AngularJS so I have created a directive which solved my problem:

Directive:

angular.module('directivesModule').directive('focusOnClear', [
    '$timeout',
    function (timeout) {
        return {
            restrict: 'A',
            link: function (scope, element, attrs) {
                var id = attrs.focusOnClear;
                var $inputSearchElement = $(element).parent().find('#' + id);
                element.on('click', function (event) {
                    event.preventDefault();
                    timeout(function () {
                        $inputSearchElement.focus();
                    }, 500);
                });
            }
        };
    }
]);

How to use the directive:

<div>
    <input type="search" id="search">
    <i class="icon-clear" ng-click="clearSearchTerm()" focus-on-clear="search"></i>
</div>

It looks like you are using jQuery, so I don't know if the directive is any help.

martinmose
  • 443
  • 8
  • 12
  • 2
    This is known not to work on ios safari. Safari specifically distinguishes between events generated by user interaction vs generated purely from the js itself, and won't deliver focus/select events this way. – Ben Wheeler Oct 16 '14 at 19:18
  • Well that is not true, and I'm using it on my apps with Cordova/Phonegap... And why down vote? How is my answer different from the others? Except I have created a Angular Directive you can use. – martinmose Oct 17 '14 at 09:33
  • usually on Angular a timeout in itself is enough, you don't need to set the timeout value. It makes it wait for the digest cycle and thus runs at a time when the other events that are hindering the focus are done. – Maarten Nov 18 '14 at 08:20
  • Hi @Maarten thanks for your comment, you mean the 500 I have set on the timeout? :) – martinmose Nov 18 '14 at 08:30
0

UPDATE

I also tried this, but to no avail:

$(document).ready(function() {
$('body :not(.wr-dropdown)').bind("click", function(e) {
    $('.test').focus();
})
$('.wr-dropdown').on('change', function(e) {
    if ($(".wr-dropdow option[value='/search']")) {
        setTimeout(function(e) {
            $('body :not(.wr-dropdown)').trigger("click");
        },3000)         
    } 
}); 

});

I am confused as to why you say this isn't working because your JSFiddle is working just fine, but here is my suggestion anyway...

Try this line of code in your SetTimeOut function on your click event:

document.myInput.focus();

myInput correlates to the name attribute of the input tag.

<input name="myInput">

And use this code to blur the field:

document.activeElement.blur();
Alex
  • 1,112
  • 4
  • 16
  • 30
  • Thank you, see my update in my question. I can't seem to make it work with your suggestion either. – matt Aug 31 '12 at 06:28
-1

Try this:

input.focus();
input.scrollIntoView()
-3

Please try using on-tap instead of ng-click event. I had this issue. I resolved it by making my clear-search-box button inside search form label and replaced ng-click of clear-button by on-tap. It works fine now.

dur
  • 15,689
  • 25
  • 79
  • 125
nishan mk
  • 1
  • 2