1

I have a select element. The selected value determines whether a text input box shows up afterwards (using ng-show). For accessibility purposes, I want the user to be able to tab seamlessly through the inputs; as soon as they tab away from the select, I want the new input box focused.

<div ng-controller="MyCtrl">
    <label>Start here, then tab through the inputs:</label>
    <div>
        <input type="text" id="firstInput" placeholder="(my first input)" />
    </div>
    <label>Do you need another input box before moving on?</label>
    <div>
        <select ng-model="needAnotherInput">
           <option value="false">No, I do not</option>
           <option value="true">Yes, I do</option>
        </select>
    </div>
    <div>
        <input ng-show="needAnotherInput" type="text" />
    </div>
    <label>Here's the next thing to fill out:</label>
    <div>
        <input type="text" />
    </div>
</div>

JSFiddle: http://jsfiddle.net/127k2urr/9/

You'll see that in Chrome and IE, this works fine; if you tab to the dropdown menu and arrow through the options (don't open the dropdown), the model updates with each new selection. But in Firefox, the model doesn't update until you tab away from the dropdown, so the new input box shows up too late; focus goes to the next one.

According to this discussion on the AngularJS GitHub, this might be related to when the browser fires the onchange event, and Firefox may be doing it properly according to spec. Regardless, I want to deal with these cases gracefully and in a generalizable way. Any solution also must account for SHIFT+TAB, which takes you backwards in the DOM through the focusable elements. In another StackOverflow thread, someone gives an example of a directive that will set focus to an element as soon as it shows up. I'll try putting something like that on the sometimes-visible next input and report back.

Community
  • 1
  • 1
melanie johnson
  • 597
  • 3
  • 16

2 Answers2

1

I realize that this solution is a jQuery solution, but it represents a technique you could implement in Angular.js.

The solution is to intercept the focus with a "busy popup" while the UI updates and then to move the focus to the next/previous field.

An example implementation is this page: http://dylanb.github.io/a11yvalid/jqvalid9.html

If you enter a date of birth that is lower than 18 years, it will emulate a server call (timeout) and then update the form with a new field for the guardian information.

The code is in this repository https://github.com/dylanb/a11yvalid

An example of a bad implementation can be found here: http://dylanb.github.io/a11yvalid/jqvalid8.html

unobf
  • 7,158
  • 1
  • 23
  • 36
  • This feels natural in your example, where a calculation is necessary, but would be awkward in mine. I'll keep it in mind for similar cases in the future, though. Thanks! See [this post](http://stackoverflow.com/questions/30111963/in-firefox-angular-model-doesnt-update-until-focus-leaves-dropdown-how-to-pro/30129561#30129561) for what I ended up doing. – melanie johnson May 08 '15 at 17:30
1

[1 PM PST: Edited to trigger change() on *any* keydown event, not just UP or DOWN, because the user can also navigate through the options by pressing character keys, e.g. letters.]

[2 PM PST: Note that this uses JQuery to trigger the handler. If you have Angular with JQLite, you can use element.triggerHandler('change').]

I went through several iterations of 'focus me when I show up (if these conditions are met) (after this amount of time)', but all of them provided a miserable screenreader experience in one browser or another.

So I ended up going with the obvious: I attached a keydown handler to the select that fires the change() event when any key is pressed.

.directive("updateOnKeydown", ['$timeout', function ($timeout) {
        return {
            restrict: 'A',
            link: function (scope, element, attrs) {
                // updates model when a key is pressed; now, when a keyboard user
                // tabs out of the dropdown after using the arrows/letter keys to make
                // a selection, the DOM is up-to-date and focus will be set to the
                // correct element
                element.bind('keydown', function (evt) {
                    evt = (!evt) ? window.event : evt;
                    var keyCode = evt.keyCode || evt.charCode;
                    if (keyCode) { // do we want to exclude anything?
                        if (!scope.$$phase) {
                            $timeout(function () {
                                $(element).change();
                            });
                        }
                        else {
                            console.log('in the middle of something!');
                        }
                    }
                });
            }
        }
    }])

and...

<select ng-model="needAnotherInput" update-on-keydown>

JSFiddle

This has worked fine everywhere I've needed it, but suggest tweaks if you'd like. (Should I be returning something from the keydown handler? Should I try again if it's in the middle of a digest cycle?)

melanie johnson
  • 597
  • 3
  • 16
  • nice solution!! So simple too :-) – unobf May 08 '15 at 18:52
  • Thanks! I just edited it to trigger change() on _any_ keyboard event, since the arrows don't handle every possible case (user can navigate thru the options w/ character keys). In [**the old solution**](http://jsfiddle.net/qzwxp0vt/2/), pressing 'Y' won't update the model, in [**the new one**](http://jsfiddle.net/qzwxp0vt/3/), it will. It turns out someone already came up with something very similar in [**this thread**](http://stackoverflow.com/a/22633038/1882961) that I somehow didn't find until now, despite hours of searching... – melanie johnson May 08 '15 at 20:25