Skip to content
This repository was archived by the owner on Oct 2, 2019. It is now read-only.

Programatically focus the select (but not activate) #201

Closed
pietschy opened this issue Sep 18, 2014 · 36 comments
Closed

Programatically focus the select (but not activate) #201

pietschy opened this issue Sep 18, 2014 · 36 comments

Comments

@pietschy
Copy link

Hi there,

I'm trying to figure out the the best way to focus the select when my page is displayed. In hunting around there's a uiSelectCtrl.activate() method but no equivalent for focus. In digging around HTML5 has an autofocus attribute but wasn't if that would make sense in SPAs or provide much value over a programatic focus mechanism.

I'm happy to do a pull request, but given I'm fairly new to angular, do you have any suggestions for approaching this?

Thanks!

@enkodellc
Copy link

I might suggest to look at this directive to help you out. I used it for a modal dialog to focus on a button so if the user hits enter after the dialog pops up it will close.

http://stackoverflow.com/questions/14833326/how-to-set-focus-on-input-field

@adharris
Copy link

In case anyone still needs this:

uiSelect uses a focusser, pretty much a dummy text entry whose purpose is to listen for focus events. It's set as the focusser property of the controller. Easiest way to focus the element would be just to focus the focusser:

// get the ui select controller
var uiSelect = angular.element(<the ui select DOM element>).controller('uiSelect');

// focus the focusser, putting focus onto select but without opening the dropdown
uiSelect.focusser[0].focus();

// Open the select without focusing the search box.  I use this on mobile to
// prevent keyboard from popping up automatically when clicking into the box
uiSelect.open = true;

// Open the select and focus:
uiSelect.activate();

@jossemarGT
Copy link

You rock @adharris !

@surabhi-batra
Copy link

@adharris, new to web programming, would appreciate any help.
The signature of the uiSelect.activate() method is - activate: function (initSearchValue, avoidReset)
Does it work for you without parameters?

@adharris
Copy link

@surabhi-batra yes it does work for me without parameters.

The first parameter, initSearchValue is a string that is put into the search box upon opening. So if you call ctrl.activate('apple') the list will be pre-filtered on the string "apple". Omitting this parameter will initialize the search to the empty string.

The second parameter avoidReset, seems to prevent the search box from being cleared when initSearchValue is missing or falsey. So calling ctrl.activate(false, true) would open the drop down and focus the search, but would not clear out any contents the user had already put in the search during a previous interaction with the select.

I was also asked how to get a reference to the uiSelect dom element as required by my first post. This is slightly tricky because uiSelect uses a directive's compile function to remove the html from the dom and replaces it with the templates. This means you cant do something like .find('ui-select'). The way i do it, which may not work for all use cases, is to wrap the ui select in a div, and then use .children to get the child elements. I do this in a directive like such:

app.directive('uiSelectWrapper', function() {
    return {
        link: function(scope, element, attrs) {
            var uiSelectController = element.children().controller('uiSelect');
        }
    }
});

and then

<div ui-select-wrapper>
    <ui-select>
        ...
    </ui-select>
</div>

@surabhi-batra
Copy link

Thanks a lot @adharris!

@MWalid
Copy link

MWalid commented Jan 5, 2015

@adharris You saved my day... thanks 👍

@kimardenmiller
Copy link

@adharris, just for clarity, when you further add the uiSelectWrapper, what do you do with the <the ui select DOM element> line to match that? That's where you want to get a reference to the uiSelect dom element as required by your first post?

@kimardenmiller
Copy link

I guess @surabhi-batra and @MWalid got this working? Using the wrapper:

.form-group ng-controller='HubController' 
    div(ui-select-wrapper)
      ui-select class='vote_hub' ng-disabled='disabled' ng-model='hubFilter.selected' on-select='setHub($item, $model); finishProp()' reset-search-input='false'
        ui-select-match id='vote_hub' placeholder='Who should see your proposal? ...' ng-enter='finishProp()' {{$select.selected.full_hub}}
        ui-select-choices refresh='refreshHubs($select.search)' refresh-delay='0' repeat='hub in hubs track by $index'
          div ng-bind-html='hub.full_hub | highlight: $select.search'

For some reason both those console.logs come up undefined: .controller('uiSelect') at the directive level:

uiSelectWrapper = ->
  link: (scope, element, attrs) ->
    uiSelectController = element.children().controller('uiSelect')
    console.log 'uiSelectController: ', uiSelectController
    uiSelectController

and focusser[0] at the controller level:

  uiSelect = angular.element 'ui-select-wrapper'
  console.log 'uiSelect: ', uiSelect.focusser[0]

Suggestions?

@rogerfar
Copy link

rogerfar commented Feb 3, 2015

When opening the select list with .activate() the focus won't be put on the searchbox, is this a bug or expected behavior?

@kimardenmiller
Copy link

@rogier21 , you were able to get a hold of the controller then using the wrapper above?

@rogerfar
Copy link

rogerfar commented Feb 4, 2015

@kimardenmiller yeah, the code works, I did exactly as adharris commented on Dec 11, 2014. But it doesn't focus the search after opening.

@kimardenmiller
Copy link

You tried uiSelect.focusser[0].focus()?

What version are you on?

@rogerfar
Copy link

rogerfar commented Feb 4, 2015

Latest release: Version: 0.9.6 - 2015-01-12T20:24:40.589Z

@stefan--
Copy link

stefan-- commented Feb 5, 2015

@kimardenmiller cannot get hold of the 'uiSelect' controller neither. Have you been able to solve it?

@kimardenmiller
Copy link

@stefan-- I have not yet solved it.

@rogier21 I'm also on v. 0.9.6., but like stefan, cannot find the controller functions described by @adharris.

console.logs come up undefined for: .controller('uiSelect') for the wrapper as configured above.

You were able to hit the controller off the wrapper to open, so uiSelect.activate() works for you?

@rogerfar
Copy link

rogerfar commented Feb 5, 2015

@kimardenmiller are you asking me?

I just do this:

var selectElement = container.find('.ui-select-container');
var uiSelect = selectElement.controller('uiSelect');
uiSelect.open = true;
uiSelect.activate(false, true);

And it pops open the dropdown list... uiSelect contains the controller if I do a console.log on it I see all the properties I would expect.

@kimardenmiller
Copy link

Interesting @rogier21. Seems you are not using the 'wrapper' idea then, correct? Seems to yield a little closer results for me, but still no actual focus happening.

Outputting uiSelect.focusser[0] to the console I now get the input element:

<input ng-disabled="$select.disabled" class="ui-select-focusser ui-select-offscreen ng-scope" type="text" aria-haspopup="true" role="button">

And uiSelect2.focusser[0].focus() runs, but does not do anything. I did my selections slightly differently, as container.find did not seem to work in my case, so I did:

    selectElement = angular.element('.ui-select-container')
    uiSelect = selectElement.controller('uiSelect')

@kimardenmiller
Copy link

Experimenting more, I found that the following will focus the ui-select, but only after an additional keystroke at the keyboard:

    $timeout ->
      element = angular.element '.ui-select-focusser'
      element.focus()  if element

Before this code runs, the previous field has focus:
image
When the above code runs, you can see that field losing focus:
image
But only after (any) keystroke do does ui-Select gain focus:
image

Thoughts on what else needs to be done fully gain focus of the ui-select input?

@timmyrosen
Copy link

I went with the wrapper directive approach and hade the same problem with the input not being focused, but got it working by adding a timeout to the activate()-method.

Like this:

    link: function(scope, element, attrs) {
            var uiSelectController = element.children().controller('uiSelect');
            console.log(uiSelectController);
            $timeout(function() {
                uiSelectController.activate(false, true);
            }, 50);
        }

@kimardenmiller
Copy link

@timmyrosen does your console.log(uiSelectController); produce the controller or undefined?

Mine produces undefined.

Can you see your html?

@timmyrosen
Copy link

@kimardenmiller My console.log gives me the controller, otherwise it wouldn't work.

Sure, here is my html:

    <ui-select-wrapper focus="true">
      <ui-select ng-model="prompt.selected" on-select="prompt.onSelect($item)">
        <ui-select-match placeholder="Sök användare">{{$select.selected.given_name || $select.selected}}</ui-select-match>
        <ui-select-choices repeat="user as user in prompt.users | filter: $select.search">
          <div ng-bind-html="user.given_name + ' ' + user.last_name | highlight: $select.search"></div>
        </ui-select-choices>
      </ui-select>
    </ui-select-wrapper>

Full directive:

app.directive('uiSelectWrapper', function($timeout) {
    return {
        restrict: 'AE',
        scope: {
            focus: '='
        },
        link: function(scope, element, attrs) {
            var uiSelectController = element.children().controller('uiSelect');
            if (scope.focus) {
                $timeout(function() {
                    uiSelectController.activate(false, true);
                }, 50);
            }
        }
    };
});

@kimardenmiller
Copy link

@timmyrosen Thank you. My controller comes back undefined and I don't know why.

Cut and pasted your code in and get TypeError: Cannot read property 'activate' of undefined

What version of Angular are you on?

@kimardenmiller
Copy link

My html is authored in Slim, then renders like so:

<div ui-select-wrapper>
  <div class="vote_hub ui-select-container ui-select-bootstrap dropdown ng-pristine ng-invalid ng-invalid-required" ng-class="{open: $select.open}" name="hubFilter" ng-disabled="disabled" ng-model="sessionSettings.hubFilter" on-select="setHub($item, $model); finishProp()" required="required" reset-search-input="false">
    <div class="btn-group ui-select-match btn-block" ng-hide="$select.open" ng-disabled="$select.disabled" ng-class="{'btn-default-focus':$select.focus}" id="vote_hub" ng-enter="finishProp()" placeholder="Who should see your proposal? ...">
      <button type="button" class="btn btn-default col-sm-10 col-md-11" ng-class="{'col-sm-8 col-md-10': $select.allowClear &amp;&amp; !$select.isEmpty(),'col-sm-10 col-md-11': !$select.allowClear || $select.isEmpty()}" tabindex="-1" ;="" ng-click="$select.activate()">
        <span ng-show="$select.isEmpty()" class="text-muted ng-binding">Who should see your proposal? ...</span> 
      <span ng-hide="$select.isEmpty()" ng-transclude="" class="ng-hide"><span class="fa fa-search ng-scope">&nbsp;</span><span class="ng-binding ng-scope"></span><span class="close ng-scope" ng-click="clear($event)">×</span></span></button> <!-- ngIf: $select.allowClear && !$select.isEmpty() --> 
    <button class="btn btn-default col-sm-2 col-md-1" ng-click="$select.activate()"><span class="caret ui-select-toggle" ng-click="$select.toggle($event)"></span></button></div><input type="text" autocomplete="off" tabindex="-1" class="form-control ui-select-search ng-pristine ng-valid ng-hide" placeholder="Who should see your proposal? ..." ng-model="$select.search" ng-show="$select.searchEnabled &amp;&amp; $select.open">
  <ul class="ui-select-choices ui-select-choices-content dropdown-menu ng-scope ng-hide" role="menu" aria-labelledby="dLabel" ng-show="$select.items.length > 0" refresh="refreshHubs($select.search)" refresh-delay="0" repeat="hub in hubs track by $index"><li class="ui-select-choices-group"><div class="divider ng-hide" ng-show="$select.isGrouped &amp;&amp; $index > 0"></div><div ng-show="$select.isGrouped" class="ui-select-choices-group-label dropdown-header ng-binding ng-hide" ng-bind-html="$group.name"></div><!-- ngRepeat: hub in $select.items track by $index --></li></ul><input ng-disabled="$select.disabled" class="ui-select-focusser ui-select-offscreen ng-scope" type="text" aria-haspopup="true" role="button"></div></div>

@timmyrosen
Copy link

@kimardenmiller Wow, that's really messy, but looking at the first element in the wrapper it should work really. I'm on Angular v 1.3.11. What does your directive look like?

@kimardenmiller
Copy link

@timmyrosen keep in mind, I took compiled code from the dom to show you. My actual code (Slim) is:

    div(ui-select-wrapper)
       ui-select( name='hubFilter' class='vote_hub'
            ng-disabled='disabled' ng-model='sessionSettings.hubFilter'
            on-select='setHub($item, $model); finishProp()'
            reset-search-input='false' required )
          ui-select-match id='vote_hub' placeholder='{{ sessionSettings.actions.hubPlaceholder }}' ng-enter='finishProp()'
            span class='fa fa-search' &nbsp
            span {{ $select.selected.full_hub }}
            span class='close' ng-click='clear($event)' &times
          ui-select-choices refresh='refreshHubs($select.search)' refresh-delay='0' repeat='hub in hubs track by $index'
            div ng-bind-html='hub.full_hub | highlight: $select.search'

I showed you compiled code in case compilation is my problem.
My directive is a cut and paste of yours (ported to CoffeeScript):

uiSelectWrapper = ($timeout) ->
  restrict: "AE"
  scope:
    focus: "="

  link: (scope, element, attrs) ->
    uiSelectController = element.children().controller("uiSelect")
    console.log 'uiSelectController Rosen: ', uiSelectController
    if scope.focus
      $timeout (->
        uiSelectController.activate false, true
      ), 50

I'm on Angular 1.9, before the big changes. So I guess it could be there.

@timmyrosen
Copy link

@kimardenmiller Angular 1.9? Do you mean 1.2.9? If so, then yeah that may be the case. If you can, I would recommend that you update to at least 1.3.

If you look at my directive you can see that I have a focus-attribute which must be set to true on the element (ui-select-wrapper) for the focus to take effect. Although if you still get undefined on the controller object, then the focus attribute doesn't really matter, and I would guess it may have something to do with the older version of angular.

@kimardenmiller
Copy link

Yes, @timmyrosen. Thanks for the careful eye on that. I actually did remove that if statement in the one I'm running:

uiSelectWrapper = ($timeout) ->
  link: (scope, element, attrs) ->
    uiSelectController = element.children().controller('uiSelect')
    console.log 'uiSelectWrapper Directive Log: ', uiSelectController
    $timeout ->
      uiSelectController
      console.log 'uiSelectWrapper Directive Log: ', uiSelectController
    , 550

So nothing on either console.log

I'll upgrade to ng 1.3 and see what it looks like then. Thanks so much for the time!

BTW: Went to see a preview of ng 2.0 last night in Mountain View. Crazy amount of changes actually. But exciting stuff. Start thinking ES6!

@dimirc
Copy link
Contributor

dimirc commented Mar 7, 2015

Take a loot at #728 and let me know your comments on the PR pls

@kimardenmiller
Copy link

@dimirc the functionality looks awesome 👍

Simple and clean from what I can tell, hiting the controller:

  ctrl.setFocus = function(){
    if (!ctrl.focus && !ctrl.multiple) ctrl.focusser[0].focus();
    if (!ctrl.focus && ctrl.multiple) _searchInput[0].focus();
  };

... from the link function:
$select.setFocus();

No extra wrappers ... nice! 😄

@dimirc
Copy link
Contributor

dimirc commented Mar 9, 2015

@kimardenmiller keep in mind that setFocus() is just added with this PR

@dimirc
Copy link
Contributor

dimirc commented Mar 9, 2015

#728 merged

@dimirc dimirc closed this as completed Mar 9, 2015
@konrness
Copy link

@kimardenmiller did you ever find the cause of your uiSelectController being undefined as of your Feb 11 comment? I'm having the same problem.

@kimardenmiller
Copy link

I didn’t, but the new focus feature (undocumented?) does seem to work.

/k

On Apr 13, 2015, at 8:49 PM, Konr Ness [email protected] wrote:

@kimardenmiller https://github.com/kimardenmiller did you ever find the cause of your uiSelectController being undefined as of your Feb 11 comment? I'm having the same problem.


Reply to this email directly or view it on GitHub #201 (comment).

@ericbolo
Copy link

I had the same problem with uiSelectController being undefined. The problem is that at the time that the link function in my wrapper directive executes, the controller has not yet been added. Setting a long enough timeout (here, 5 seconds to make sure) "solved" it:

$timeout(function () {
var selectElement = angular.element('.ui-select-container')
var uiSelect = selectElement.controller('uiSelect')
console.log("controller", uiSelect)
}, 5000)

Obviously a hack. Any event I could use to figure out when the controller has been initialized?

@jb41997
Copy link

jb41997 commented Jun 3, 2015

Quick question.
I have ui-select working great, but I'm trying to prevent the keyboard from popping on mobile devices when the dropdown is selected. I'm having a hard time grasping exactly how this is achieved using the methods outlined above. What is the best way to handle this considering the new setFocus() functionality etc.? A quick explanation from start to fin would be greatly appreciated for us noobs!

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

No branches or pull requests