Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Autofocus doesn't work in modals #938

Closed
studds opened this issue Oct 21, 2016 · 18 comments
Closed

Autofocus doesn't work in modals #938

studds opened this issue Oct 21, 2016 · 18 comments

Comments

@studds
Copy link

studds commented Oct 21, 2016

Feature description:

I've got an input in my modal. I'd like it to autofocus when the modal opens. I've added the autofocus attribute and this works the first time the modal is opened in Google Chrome, but not subsequently. It doesn't seem to work at all in Safari. I haven't tested other browsers.

As a workaround, I've created an attribute directive that focuses the input during ngAfterViewInit, but this seems like using a sledge hammer to crack a nut (and it could create unexpected behaviour elsewhere.)

Link to minimally-working plunker that reproduces the issue:

http://plnkr.co/edit/At2FNwl9lKGF3sk2gTbt?p=preview

Version of Angular, ng-bootstrap, and Bootstrap:

Angular: v2.0.1

ng-bootstrap: v1.0.0-alpha.5

Bootstrap: v4.0.0-alpha.4

@pkozlowski-opensource
Copy link
Member

Hmm, indeed. At this point I would assume that this is an issue on our side that will be fixed. But it needs investigation as at this point I'm not sure what is going on. Thnx for reporting!

@pkozlowski-opensource
Copy link
Member

So it seems that my naive implementation can't really work as the autofocus attribute seems to be only interpreted once (and for some browsers only on page load). Here is a plunker that, when opened in Chrome, shows that autofocs won't work in modal even for the first time if autofocus is used previously: http://plnkr.co/edit/MHIADYDFdxNmdZMNzEW2?p=preview

Given the above the only real solution is to query for elements with autofocus but we need to make sure that this doesn't break server-side rendering with Universal.

@pkozlowski-opensource
Copy link
Member

BTW: I'm marking it as a feature since Bootstrap 4 doesn't support it.

@studds
Copy link
Author

studds commented Nov 2, 2016

You're right, Chrome only autofocuses the first element. Safari seems to autofocus any newly-created element - not sure why this wouldn't work with modals, but it doesn't. Even so, this doesn't help, because browsers are behaving inconsistently.

Would a PR help with this? If so, any tips on where to hook this in?

@tmcintire
Copy link

Has there been any development on this? I'm running into this problem as well

@laserus
Copy link

laserus commented Feb 7, 2017

Solution to this is rather simple. Your own focus directive. I even do not understand why this directive is not default in Angular core.

http://plnkr.co/edit/2euC1MDHup7x4ShJcXQl?p=preview

import { Directive, Input, ElementRef } from '@angular/core';

@Directive({
	selector : '[focus]'
})
export class FocusDirective {
	@Input()
	focus : boolean;

	constructor(private element : ElementRef) {
	}

	protected ngOnChanges() {
		if (this.focus)
			this.element.nativeElement.focus();
	}
}

@kohoutjosef
Copy link

@laserus
this focus directive causes scrolling to the bottom of the page which is underneath the modal window

@laserus
Copy link

laserus commented Aug 24, 2017

@kohoutjosef you mean you want to fix the scrollTop?
http://plnkr.co/edit/SrxnejqJ4Llugbs6isN9?p=preview

@laserus
Copy link

laserus commented Aug 24, 2017

It most likely is done because the new modal first is created at bottom, and when styles applied it becomes overlay. So it scrolls to position of input at the moment of creation of modal, but just before it changes position to absolute?

It is just wild guess, I do not know the reason, probably related:
https://stackoverflow.com/questions/10719848/scroll-page-on-text-input-focus-for-mobile-devices

But issue is not with directive itself but how the focus working.

@kohoutjosef
Copy link

yes the directive is OK but the problem is its application onto modal.
I would say it is caused by a fact that when opening a modal you have to set an overflow to hidden in the html body. However that happens in my case only after srolling down to the bottom of the page

@kohoutjosef
Copy link

a little bit hack, but functional (i hope so):

      const top = document.body.scrollTop;
      const left = document.body.scrollLeft;

      this.element.nativeElement.focus();

      document.body.scrollTop = top;
      document.body.scrollLeft = left;

i would like to a better solution

@frederikprijck
Copy link

frederikprijck commented Nov 28, 2017

@kohoutjosef Less hacky might be to use a setTimeout, wrapped around the focus() call (still hacky tho 😞 ).

@pkozlowski-opensource Using setTimeout probably indicates it might be a timing issue, not sure if this helps u (not sure if the issue is related to ng-bootstrap neither).
Here's a reproduction of focussing an input when opening the modal: http://plnkr.co/edit/i7BeFQle0PsWQiimY1Bf?p=preview If you wrap the select() call in a setTimeout, avoids the scrolling behavior. I tried using both AfterViewInit and AfterViewChecked, so I'm unsure what we can do in order to ensure the focus isn't called too soon...

Note: My issue is not related to autofocus, but calling focus() in general inside a modal. I wasn't sure if this comment would be better here or at #1776

@westerncj
Copy link

I'm having a similar issue. I set autofocus on the first input element, which works fine when the modal is first opened. Is there a way to set focus when I close then reopen the modal on that same input?

@pkozlowski-opensource
Copy link
Member

Based on https://getbootstrap.com/docs/4.1/components/modal/:

Due to how HTML5 defines its semantics, the autofocus HTML attribute has no effect in Bootstrap modals. To achieve the same effect, use some custom JavaScript

Given the above I wonder if we should work-around it here. The "solution" would basically boil down to:

  • waiting until view is rendered (ngAfterViewInit)
  • doing DOM query for any element with the autofocus attribute
  • setting focus on this element if found (or proceeding with default focus set).

@benouat since you've been working on focus in modals - what is your opinion on this?

@benouat
Copy link
Member

benouat commented May 3, 2018

For anything related to focus like that I would recommend to use the zone for that.
Main idea here is to perform the focus after being sure all updates have been done, and DOM is stable.

/!\ disclaimer, this might be scary code :)

this.ngZone.onStable.asObservable().pipe(take(1)).subscribe(() => {
  const element = this.element.nativeElement.querySelector('[autofocus]');
  if (element) {
    element.focus();
  }
});  

@mboughaba
Copy link

@benouat the provided workaround doesn't works only the first time the modal is open.
One can acheive the same results without having to deal with NgZone

    const elem: any = this.element.nativeElement.querySelector('[autofocus]');
    if (elem) {
      elem.focus();
    }

@benouat
Copy link
Member

benouat commented Jun 11, 2018

As I mentioned:

Main idea here is to perform the focus after being sure all updates have been done, and DOM is stable.

NgZone is needed here. You want to do that only when DOM is stable. Of course it might work without it, but something you'll see it won't !

So to me, the idea here remains the same as @pkozlowski-opensource described in his last comment, you have to create a directive that will do:

  1. waiting until view is rendered (ngAfterViewInit)
  2. doing DOM query for any element with the autofocus attribute
  3. setting focus on this element if found (or proceeding with default focus set) with NgZone involved here

@pkozlowski-opensource
Copy link
Member

Closing in favour of #2728

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants