Search code examples
angularjssafarimobile-safari

dynamic add input in ng-repeat causes last element to steal focus in safari


I'd like users to be able to enter multiple answers to a question, so I have an ng-repeat of text inputs that grows as the user adds more answers.

This is working fine in chrome, but in safari, the last element steals the focus as soon as it's added spreading a fast typing user's answer across several inputs.

In iOS safari it's even worse, as it's causing the browser to hang.

Here's the code for the form

<form class="form-horizontal sparse" 
        ng-controller="AssessmentController as assessment">
    <fieldset>
        <div class="form-group alt-uses-list" ng-repeat="response in answer.responses">
            <label for="answer{{$index}}" class="col-xs-1 control-label">{{$index + 1}}.</label>
            <div class="col-xs-10">
                <input type="text" class="form-control" 
                    id="response.uses{{$index}}"
                    autocomplete="off" tabIndex="{{$index + 1}}" 
                    autofocus="{{$first}}" ng-model="response.value"
                    ng-change="assessment.addAnswerIfNeeded()">
            </div>
        </div>
    </fieldset>
</form>

Here's the controller

function AssessmentController($scope, $log) {
    $scope.answer = {};
    $scope.answer.responses = [{id:0, value:""},{id:1, value:""}];

    this.addAnswerIfNeeded = addAnswerIfNeeded;

    // Add answer if the last two answers are non-empty
    function addAnswerIfNeeded() {
      var answers = $scope.answer.responses;

      if ((answers[answers.length - 1].value != '') || 
          (answers[answers.length - 2].value != '')) {
        addAnswer();
      }
    }

    // Adds a new answer if this is the last element
    // isLast is needed to prevent non-lazy evaluation bugs
    function addAnswer() {
        $scope.answer.responses.push({
             id:$scope.answer.responses.length, value: ""
        });
    };
}

I've created a jsfiddle.


Solution

  • The autofocus attribute is a Boolean type.

    According to this link:

    The value passed as the first parameter is converted to a boolean value, if necessary. If value is omitted or is 0, -0, null, false, NaN, undefined, or the empty string (""), the object has an initial value of false. All other values, including any object or the string "false", create an object with an initial value of true.

    Do not confuse the primitive Boolean values true and false with the true and false values of the Boolean object

    autofocus="{{$first}}" generates autofocus="false" on any item that is not the first item in the ng-repeat. As the link states

    All other values, including any object or the string "false", create an object with an initial value of true.

    If you can update to a angular JS version that includes the ng-attr directive you can update your code to be the following, I have updated you jsfiddle to show a working example

    <form class="form-horizontal sparse" ng-controller="AssessmentController as assessment">
      <fieldset>
        <div class="form-group alt-uses-list" ng-repeat="response in answer.responses">
          <label for="answer{{$index}}" class="col-xs-1 control-label">{{$index + 1}}.</label>
          <div class="col-xs-10">
            <input type="text" class="form-control" id="response.uses{{$index}}" autocomplete="off" tabIndex="{{$index + 1}}" ng-attr-autofocus="{{$first||undefined}}" ng-model="response.value" ng-change="assessment.addAnswerIfNeeded()">
          </div>
        </div>
      </fieldset>
    </form>
    

    When the ng-attr-autofocus="{{$first||undefined}}" evaluates to false, the autofocus attribute gets the value of undefined and thus the autofocus gets removed from the dom.