Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Provide a standard way to manage focus #2012

Closed
ewinslow opened this issue Feb 14, 2013 · 15 comments
Closed

Provide a standard way to manage focus #2012

ewinslow opened this issue Feb 14, 2013 · 15 comments

Comments

@ewinslow
Copy link
Contributor

Listening for focus/blur events is nice, but it frequently comes up that I need to set the focus on a certain element. E.g. clicking an "add comment" button opens up text field and sets focus inside the textarea. Clicking submit/cancel closes the form and puts focus back on the "add comment" button.

Related: #903 and #1277.

My initial thought was ng-focus-model="some.model". Setting some.model to true in the controller would then move focus to the element. But after experimenting with this, I've found that to be pretty unintuitive. This is one of the few cases where reaching into the DOM and just calling element.focus() seems like the right thing to do. Perhaps angular could provide a way to access dom elements from controllers (heresy!!) but with a severly limited API (i.e. only supports focus())

@pkozlowski-opensource
Copy link
Member

Some discussion regarding this in #1250 as well

@elgerlambert
Copy link

There has been quite some discussion around this topic, so a proper solution requires more effort no doubt, but I thought I'd share the following which has served me well so far.

angular.module('cn')
  .directive('cnFocusWhen', [function () {
    return function postLink(scope, element, attrs) {
      scope.$watch(attrs.cnFocusWhen, function( autofocus ) {
        if ( autofocus )
          setTimeout( function () {
            element[0].focus()
          }, 0)
      })
    }
  }])

Sounds like my implementation is similar to that of @ewinslow's, the only real difference being syntax; "focus-model" to me implies it should only watch model properties, whereas I usually have a $scope.state object separate to my $scope.form model.

@elgerlambert
Copy link

I opted for "focus-when", but I suppose ng-autofocus might fit better with Angular's motto "HTML enhanced"; adding the attribute autofocus to the element when ng-autofocus evaluates to true, similar to how ng-required works..

// **Not tested!**
angular.module('cn')
  .directive('cnAutofocus', [function () {
    return function postLink(scope, element, attrs) {
      scope.$watch(attrs.cnAutofocus, function( autofocus ) {
        if ( autofocus ) element.attr('autofocus','true')
        else element.removeAttr('autofocus')
      })
    }
  }])

@benlesh
Copy link
Contributor

benlesh commented Aug 18, 2013

Since this is a large change set, I've started a discussion on the mailing list here about it: https://groups.google.com/forum/?hl=en#!topic/angular/EQSWV1chf0A

The spirit of the addition, at this point, is that I'm implementing a new controller, similar to an NgModelController, but seperate, as it only worries about managing focus. More details can be found at the link.

I'll have a pull request ready soon.

@benlesh
Copy link
Contributor

benlesh commented Aug 19, 2013

I have the code ready, but I'm unable to build and run tests.

I want to get this done and submit a pull request for your review, but currently I can't until I get this resolved #3648

@kevin-smets
Copy link

$timeout or setTimeout works great indeed, but not so on mobile safari. You have to set the focus inside the event loop of ngClick in order for focus to work properly on iOS (triggering the keyboard), so any timeouts are off limits. I've looked around but found no mention of this problem anywhere, I can't imagine no one else encountered this yet though...

The only way to go around this is by using event listeners, and $apply if it needs to update the model. Of course I would like to use the standard ngClick, it just doesn't seem possible at the moment without it throwing an error.

@bittenbytailfly
Copy link

I'm fairly new to angular, but coming from KnockoutJS, I tried to replicate the 'hasfocus' data binding (see http://knockoutjs.com/documentation/hasfocus-binding.html) which seems to achieve what you want. Here's my effort:

.directive('dbFocus', [function () {
        return {
            restrict: 'A',
            scope: {
                dbFocus: '='
            },
            link: function (scope, elem, attrs) {
                scope.$watch('dbFocus', function (newval, oldval) {
                    if (newval) {
                        elem[0].focus();
                    }
                }, true);
                elem.bind('blur', function () {
                    scope.$apply(function () { scope.dbFocus = false; });
                });
                elem.bind('focus', function () {
                    if (!scope.dbFocus) {
                        scope.$apply(function () { scope.dbFocus = true; });
                    }
                });
            }
        };
    }]);

It's probably not the most efficient way to go, but like I said I'm new so go easy on me! I prefixed it 'db' simply because that matches the app I was working on at the time.

@RoyceLithgo
Copy link

Any progress on this? I've been racking my brain trying to get my directive to support the html5 "autofocus" boolean attribute, but angular seems determined not to allow me to do it.

@bittenbytailfly
Copy link

RoyceLithgo - as I understand it, the autofocus attribute in html5 is only relevant on page load and does not effect behaviour after that.

Have you tried the directive I listed above? I used it successfully in a Chrome extension but have not cross browser tested it. I see no reason why it would fail though, unless I've misunderstood what you are trying to achieve.

Let me know if it's of any use to you.

@RoyceLithgo
Copy link

Actually in the end I did something similar to get the autofocus supported in my directive. I added autofocus to my isolate scope and did this in the linking function:

if(scope.autofocus === "true")
{
elm[0].childNodes[1].focus();
}

In my case the element was nested in the directive template, hence the path above. It works, but it does need you to set a value on the autofocus attribute whereas the html5 autofocus attribute doesn't need one. My logic was a lot simpler than yours, mainly because it doesn't support 2-way binding (in my use case, I will always set autofocus to a static "true" value on the first element in the form).

@btford
Copy link
Contributor

btford commented May 21, 2014

I think because the use cases for such a directive might differ, this is a good opportunity for 3rd party modules to implement different solutions.

http://github.com/jgallen23/angular-focus looks like a good start.

I'm going to close this as "not in core," but feel free to post solutions or links to modules here for others to find. Thanks!

@benlesh
Copy link
Contributor

benlesh commented May 21, 2014

I'm not sure I disagree, or agree. Given that there is a method for managing element validity built into Angular, why would a method for managing element focus in a similar fashion not also be a core feature? It might be nice for use in more primitive directives, like a custom calendar control for example, or maybe a custom color picker. Something were more fine-grained focus control might prove useful.

Honest question. Thanks for your time, @btford.

@btford
Copy link
Contributor

btford commented May 21, 2014

honestly I think the form validation stuff should also be a separate module

@benlesh
Copy link
Contributor

benlesh commented May 21, 2014

Fair enough. I think I can agree with that.

@benlesh
Copy link
Contributor

benlesh commented May 21, 2014

Hahaha... my last comment reads funny. I'm sure you were just waiting around to see if I'd agree with you. ;) lol

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

No branches or pull requests

8 participants