angularjs - Como definir o foco no campo de entrada?




angularjs ng attr title (20)

Qual é o 'caminho angular' para definir o foco no campo de entrada no AngularJS?

Requisitos mais específicos:

  1. Quando um Modal é aberto, defina o foco em um <input> predefinido dentro deste Modal.
  2. Toda vez que <input> se torna visível (por exemplo, clicando em algum botão), defina o foco nele.

Eu tentei alcançar o primeiro requisito com autofocus , mas isso funciona apenas quando o Modal é aberto pela primeira vez, e somente em certos navegadores (por exemplo, no Firefox ele não funciona).

Qualquer ajuda será apreciada.


  1. Quando um Modal é aberto, defina o foco em um <input> predefinido dentro deste Modal.

Defina uma diretiva e faça com que ela assista a uma propriedade / trigger para saber quando focalizar o elemento:

Name: <input type="text" focus-me="shouldBeOpen">

app.directive('focusMe', ['$timeout', '$parse', function ($timeout, $parse) {
    return {
        //scope: true,   // optionally create a child scope
        link: function (scope, element, attrs) {
            var model = $parse(attrs.focusMe);
            scope.$watch(model, function (value) {
                console.log('value=', value);
                if (value === true) {
                    $timeout(function () {
                        element[0].focus();
                    });
                }
            });
            // to address @blesh's comment, set attribute value to 'false'
            // on blur event:
            element.bind('blur', function () {
                console.log('blur');
                scope.$apply(model.assign(scope, false));
            });
        }
    };
}]);

Plunker

O $ timeout parece ser necessário para dar o tempo modal para renderizar.

'2.' Toda vez que <input> se torna visível (por exemplo, clicando em algum botão), defina o foco nele.

Crie uma diretiva essencialmente como a acima. Observe alguma propriedade de escopo e, quando ela se tornar verdadeira (configure-a no manipulador ng-click), execute o element[0].focus() . Dependendo do seu caso de uso, você pode ou não precisar de um tempo limite $ para este:

<button class="btn" ng-click="showForm=true; focusInput=true">show form and
 focus input</button>
<div ng-show="showForm">
  <input type="text" ng-model="myInput" focus-me="focusInput"> {{ myInput }}
  <button class="btn" ng-click="showForm=false">hide form</button>
</div>

app.directive('focusMe', function($timeout) {
  return {
    link: function(scope, element, attrs) {
      scope.$watch(attrs.focusMe, function(value) {
        if(value === true) { 
          console.log('value=',value);
          //$timeout(function() {
            element[0].focus();
            scope[attrs.focusMe] = false;
          //});
        }
      });
    }
  };
});

Plunker

Atualização 7/2013 : vi algumas pessoas usarem minhas diretivas de escopo originais isoladas e, em seguida, ter problemas com campos de entrada incorporados (isto é, um campo de entrada no modal). Uma diretiva sem um novo escopo (ou possivelmente um novo escopo da criança) deve aliviar um pouco da dor. Então, acima eu atualizei a resposta para não usar escopos isolados. Abaixo está a resposta original:

Resposta original para 1., usando um escopo isolado:

Name: <input type="text" focus-me="{{shouldBeOpen}}">

app.directive('focusMe', function($timeout) {
  return {
    scope: { trigger: '@focusMe' },
    link: function(scope, element) {
      scope.$watch('trigger', function(value) {
        if(value === "true") { 
          $timeout(function() {
            element[0].focus(); 
          });
        }
      });
    }
  };
});

Plunker

Resposta original para 2., usando um escopo isolado:

<button class="btn" ng-click="showForm=true; focusInput=true">show form and
 focus input</button>
<div ng-show="showForm">
  <input type="text" focus-me="focusInput">
  <button class="btn" ng-click="showForm=false">hide form</button>
</div>

app.directive('focusMe', function($timeout) {
  return {
    scope: { trigger: '=focusMe' },
    link: function(scope, element) {
      scope.$watch('trigger', function(value) {
        if(value === true) { 
          //console.log('trigger',value);
          //$timeout(function() {
            element[0].focus();
            scope.trigger = false;
          //});
        }
      });
    }
  };
});

Plunker

Como precisamos redefinir a propriedade trigger / focusInput na diretiva, '=' é usado para vinculação de dados bidirecional. Na primeira diretiva, '@' era suficiente. Observe também que, ao usar '@', comparamos o valor do acionador como "true", pois @ sempre resulta em uma string.


(EDIT: eu adicionei uma solução atualizada abaixo desta explicação)

Mark Rajcok é o homem ... e sua resposta é uma resposta válida, mas teve um defeito (desculpe Mark) ...

... Tente usar o booleano para focar na entrada, desfoque a entrada e tente usá-la para focar a entrada novamente. Ele não funcionará a menos que você redefina o booleano para false, então $ digest, então redefina de volta para true. Mesmo se você usar uma comparação de strings em sua expressão, você será forçado a mudar a string para outra coisa, $ digest, e então alterá-la de volta. (Isso foi resolvido com o manipulador de eventos de desfoque).

Então eu proponho esta solução alternativa:

Use um evento, o recurso esquecido do Angular.

JavaScript adora eventos depois de tudo. Os eventos são inerentemente fracamente acoplados e, melhor ainda, você evita adicionar outro $ watch ao seu $ digest.

app.directive('focusOn', function() {
   return function(scope, elem, attr) {
      scope.$on(attr.focusOn, function(e) {
          elem[0].focus();
      });
   };
});

Então agora você poderia usá-lo assim:

<input type="text" focus-on="newItemAdded" />

e depois em qualquer lugar no seu aplicativo ...

$scope.addNewItem = function () {
    /* stuff here to add a new item... */

    $scope.$broadcast('newItemAdded');
};

Isso é incrível porque você pode fazer todo tipo de coisa com algo assim. Por um lado, você poderia amarrar em eventos que já existem. Para outra coisa, você começa a fazer algo inteligente fazendo com que partes diferentes do seu aplicativo publiquem eventos que outras partes do seu aplicativo podem assinar.

De qualquer forma, esse tipo de coisa grita "evento dirigido" para mim. Eu acho que, como desenvolvedores Angular, tentamos arrasar os pinos em forma de escopo em forma de evento.

É a melhor solução? Não sei. É uma solução.

Solução atualizada

Após o comentário do @ ShimonRachlenko abaixo, mudei o meu método de fazer isso um pouco. Agora eu uso uma combinação de um serviço e uma diretiva que lida com um evento "nos bastidores":

Fora isso, é o mesmo princípio descrito acima.

Aqui está uma demonstração rápida Plunk

Uso

<input type="text" focus-on="focusMe"/>
app.controller('MyCtrl', function($scope, focus) {
    focus('focusMe');
});

Fonte

app.directive('focusOn', function() {
   return function(scope, elem, attr) {
      scope.$on('focusOn', function(e, name) {
        if(name === attr.focusOn) {
          elem[0].focus();
        }
      });
   };
});

app.factory('focus', function ($rootScope, $timeout) {
  return function(name) {
    $timeout(function (){
      $rootScope.$broadcast('focusOn', name);
    });
  }
});

Apenas um novato aqui, mas eu era capaz de fazê-lo funcionar em um http://angular-ui.github.io/bootstrap/#/modal com esta diretiva:

directives.directive('focus', function($timeout) {
    return {
        link : function(scope, element) {
            scope.$watch('idToFocus', function(value) {
                if (value === element[0].id) {
                    $timeout(function() {
                        element[0].focus();
                    });
                }
            });
        }
    };
});

e no método $ modal.open usei o seguinte para indicar o elemento onde o foco deve ser colocado:

var d = $modal.open({
        controller : function($scope, $modalInstance) {
            ...
            $scope.idToFocus = "cancelaAteste";
    }
        ...
    });

no template eu tenho isso:

<input id="myInputId" focus />

Aqui está a minha solução original:

plunker

var app = angular.module('plunker', []);
app.directive('autoFocus', function($timeout) {
    return {
        link: function (scope, element, attrs) {
            attrs.$observe("autoFocus", function(newValue){
                if (newValue === "true")
                    $timeout(function(){element[0].focus()});
            });
        }
    };
});

E o HTML:

<button ng-click="isVisible = !isVisible">Toggle input</button>
<input ng-show="isVisible" auto-focus="{{ isVisible }}" value="auto-focus on" />

O que faz:

Foca a entrada conforme ela se torna visível com ng-show. Não use $ watch ou $ aqui.


Eu achei útil usar uma expressão geral. Desta forma, você pode fazer coisas como mover automaticamente o foco quando o texto de entrada é válido

<button type="button" moo-focus-expression="form.phone.$valid">

Ou focar automaticamente quando o usuário conclui um campo de tamanho fixo

<button type="submit" moo-focus-expression="smsconfirm.length == 6">

E, claro, foco após carga

<input type="text" moo-focus-expression="true">

O código da diretiva:

.directive('mooFocusExpression', function ($timeout) {
    return {
        restrict: 'A',
        link: {
            post: function postLink(scope, element, attrs) {
                scope.$watch(attrs.mooFocusExpression, function (value) {

                    if (attrs.mooFocusExpression) {
                        if (scope.$eval(attrs.mooFocusExpression)) {
                            $timeout(function () {
                                element[0].focus();
                            }, 100); //need some delay to work with ng-disabled
                        }
                    }
                });
            }
        }
    };
});

Eu edito a diretiva focusMe de Mark Rajcok para trabalhar com foco múltiplo em um elemento.

HTML:

<input  focus-me="myInputFocus"  type="text">

no AngularJs Controller:

$scope.myInputFocus= true;

Diretiva AngulaJS:

app.directive('focusMe', function ($timeout, $parse) {
    return {
        link: function (scope, element, attrs) {
            var model = $parse(attrs.focusMe);
            scope.$watch(model, function (value) {
                if (value === true) {
                    $timeout(function () {
                        scope.$apply(model.assign(scope, false));
                        element[0].focus();
                    }, 30);
                }
            });
        }
    };
});

Eu escrevi uma diretiva de foco de ligação bidirecional, assim como o modelo recentemente.

Você pode usar a diretiva de foco como esta:

<input focus="someFocusVariable">

Se você tornar a variável de escopo someFocusVariable true em qualquer lugar no seu controlador, a entrada será focada. E se você quiser "desfocar" sua entrada, someFocusVariável pode ser definido como falso. É como a primeira resposta de Mark Rajcok, mas com ligação bidirecional.

Aqui está a diretiva:

function Ctrl($scope) {
  $scope.model = "ahaha"
  $scope.someFocusVariable = true; // If you want to focus initially, set this to true. Else you don't need to define this at all.
}

angular.module('experiement', [])
  .directive('focus', function($timeout, $parse) {
    return {
      restrict: 'A',
      link: function(scope, element, attrs) {
          scope.$watch(attrs.focus, function(newValue, oldValue) {
              if (newValue) { element[0].focus(); }
          });
          element.bind("blur", function(e) {
              $timeout(function() {
                  scope.$apply(attrs.focus + "=false"); 
              }, 0);
          });
          element.bind("focus", function(e) {
              $timeout(function() {
                  scope.$apply(attrs.focus + "=true");
              }, 0);
          })
      }
    }
  });

Uso:

<div ng-app="experiement">
  <div ng-controller="Ctrl">
    An Input: <input ng-model="model" focus="someFocusVariable">
    <hr>
        <div ng-click="someFocusVariable=true">Focus!</div>  
        <pre>someFocusVariable: {{ someFocusVariable }}</pre>
        <pre>content: {{ model }}</pre>
  </div>
</div>

Aqui está o violino:

http://fiddle.jshell.net/ubenzer/9FSL4/8/


Eu não acho que $ timeout seja uma boa maneira de focar o elemento na criação. Aqui está um método usando a funcionalidade angular integrada, escavada a partir das profundezas escuras dos documentos angulares. Observe como o atributo "link" pode ser dividido em "pré" e "post", para funções de pré-link e pós-link.

Exemplo de trabalho: http://plnkr.co/edit/Fj59GB

// this is the directive you add to any element you want to highlight after creation
Guest.directive('autoFocus', function() {
    return {
        link: {
            pre: function preLink(scope, element, attr) {
                console.debug('prelink called');
                // this fails since the element hasn't rendered
                //element[0].focus();
            },
            post: function postLink(scope, element, attr) {
                console.debug('postlink called');
                // this succeeds since the element has been rendered
                element[0].focus();
            }
        }
    }
});
<input value="hello" />
<!-- this input automatically gets focus on creation -->
<input value="world" auto-focus />

Documentos da Diretriz Full AngularJS: https://docs.angularjs.org/api/ng/service/$compile


Isso funciona bem e uma maneira angular de concentrar o controle de entrada

angular.element('#elementId').focus()

Esta não é uma forma angular pura de executar a tarefa, mas a sintaxe segue o estilo angular. Jquery desempenha papel indiretamente e acessa diretamente DOM usando Angular (jQLite => JQuery Light).

Se necessário, esse código pode ser facilmente colocado dentro de uma diretiva angular simples, na qual o elemento é diretamente acessível.


Isso também é possível usar ngModelController . Trabalhando com 1.6+ (não sei com versões mais antigas).

HTML

<form name="myForm">
    <input type="text" name="myText" ng-model="myText">
</form>

JS

$scope.myForm.myText.$$element.focus();

-

NB: Dependendo do contexto, talvez você precise incluir uma função de tempo limite.

NB²: Ao usar controllerAs , isso é quase o mesmo. Apenas substitua name="myForm" por name="vm.myForm" e em JS, vm.myForm.myText.$$element.focus(); .


Não para ressuscitar um zumbi ou ligar minha própria diretiva (ok, é exatamente isso que eu estou fazendo):

https://github.com/hiebj/ng-focus-if

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

<input focus-if />

(function() {
    'use strict';
    angular
        .module('focus-if', [])
        .directive('focusIf', focusIf);

    function focusIf($timeout) {
        function link($scope, $element, $attrs) {
            var dom = $element[0];
            if ($attrs.focusIf) {
                $scope.$watch($attrs.focusIf, focus);
            } else {
                focus(true);
            }
            function focus(condition) {
                if (condition) {
                    $timeout(function() {
                        dom.focus();
                    }, $scope.$eval($attrs.focusDelay) || 0);
                }
            }
        }
        return {
            restrict: 'A',
            link: link
        };
    }
})();

Para aqueles que usam o Angular com o plugin Bootstrap:

http://angular-ui.github.io/bootstrap/#/modal

Você pode conectar-se à promessa opened da instância modal:

modalInstance.opened.then(function() {
        $timeout(function() {
            angular.element('#title_input').trigger('focus');
        });
    });

modalInstance.result.then(function ( etc...

Provavelmente, a solução mais simples na era do ES6.

Adicionar a seguinte diretiva de um liner torna o atributo HTML 'autofocus' efetivo em Angular.js.

.directive('autofocus', ($timeout) => ({link: (_, e) => $timeout(() => e[0].focus())}))

Agora, você pode usar a sintaxe de autofoco HTML5 como:

<input type="text" autofocus>

Se você estiver usando o modalInstance e tiver o objeto, poderá usar "then" para executar ações depois de abrir o modal. Se você não estiver usando o modalInstance, e codificado para abrir o modal, você pode usar o evento. O $ timeout não é uma boa solução.

Você pode fazer (Bootstrap3):

$("#" + modalId).on("shown.bs.modal", function() {
    angular.element("[name='name']").focus();
});

Em modalInstance você pode olhar para a biblioteca como executar o código após o modal aberto.

Não use $ timeout como este, o $ timeout pode ser 0, 1, 10, 30, 50, 200 ou mais, isso dependerá do computador cliente e do processo para abrir o modal.

Não use $ timeout, deixe o método te dizer quando você pode se concentrar;)

Espero que isso ajude! :)


Toda a resposta anterior não funciona se o elemento de foco desejado for injetado em um modelo de diretiva. A seguinte diretriz ajusta-se ao elemento simples ou ao elemento injetado da diretiva (eu o escrevi no texto datilografado ). aceita o seletor para o elemento focalizável interno. se você só precisa focar o elemento self - não envie nenhum parâmetro seletor para a diretiva:

module APP.Directives {

export class FocusOnLoadDirective implements ng.IDirective {
    priority = 0;
    restrict = 'A';

    constructor(private $interval:any, private $timeout:any) {

    }

    link = (scope:ng.IScope, element:JQuery, attrs:any) => {
        var _self = this;
        var intervalId:number = 0;


        var clearInterval = function () {
            if (intervalId != 0) {
                _self.$interval.cancel(intervalId);
                intervalId = 0;
            }
        };

        _self.$timeout(function(){

                intervalId = _self.$interval(function () {
                    let focusableElement = null;
                    if (attrs.focusOnLoad != '') {
                        focusableElement = element.find(attrs.focusOnLoad);
                    }
                    else {
                        focusableElement = element;
                    }
                    console.debug('focusOnLoad directive: trying to focus');
                    focusableElement.focus();
                    if (document.activeElement === focusableElement[0]) {
                        clearInterval();
                    }
                }, 100);

                scope.$on('$destroy', function () {
                    // Make sure that the interval is destroyed too
                    clearInterval();
                });

        });
    };

    public static factory = ():ng.IDirectiveFactory => {
        let directive = ($interval:any, $timeout:any) => new FocusOnLoadDirective($interval, $timeout);
        directive.$inject = ['$interval', '$timeout'];
        return directive;
    };
}

angular.module('common').directive('focusOnLoad', FocusOnLoadDirective.factory());

}

exemplo de uso para elemento simples:

<button tabindex="0" focus-on-load />

exemplo de uso para elemento interno (geralmente para elemento injetado dinâmico como diretiva com modelo):

<my-directive focus-on-load="input" />

você pode usar qualquer seletor jQuery em vez de "input"


Um simples que funciona bem com modais:

.directive('focusMeNow', ['$timeout', function ($timeout)
{
    return {
        restrict: 'A',

        link: function (scope, element, attrs)
        {


            $timeout(function ()
            {
                element[0].focus();
            });



        }
    };
}])

Exemplo

<input ng-model="your.value" focus-me-now />

Você também pode usar a funcionalidade jqlite incorporada em angular.

angular.element('.selector').trigger('focus');


É fácil .. tente isso

html

<select id="ddl00">  
 <option>"test 01"</option>  
</select>

javascript

document.getElementById("ddl00").focus();

Eu quero contribuir para essa discussão depois de procurar por uma solução melhor e não encontrá-la, tendo que criá-la.

Critérios: 1. A solução deve ser independente do escopo do controlador pai para aumentar a reutilização. 2. Evite o uso de $ watch para monitorar alguma condição, isso é lento, aumenta o tamanho do loop digest e dificulta os testes. 3. Evite $ timeout ou $ scope $ apply () para acionar um loop digest. 4. Um elemento de entrada está presente no elemento em que a directiva é utilizada em aberto.

Essa é a solução que eu mais gostei:

Directiva:

.directive('focusInput', [ function () {
    return {
        scope: {},
        restrict: 'A',
        compile: function(elem, attr) {
            elem.bind('click', function() {
                elem.find('input').focus();                
            });
        }        
    };
}]);

Html:

 <div focus-input>
     <input/>
 </div>

Espero que isso ajude alguém lá fora!


Se você deseja definir o foco em determinado elemento, você pode usar a abordagem abaixo.

  1. Crie um serviço chamado focus.

    angular.module('application')
    .factory('focus', function ($timeout, $window) {
        return function (id) {
            $timeout(function () {
                var element = $window.document.getElementById(id);
                if (element)
                    element.focus();
            });
        };
    });
  2. Injete no controlador de onde você deseja ligar.

  3. Ligue para este serviço.







angularjs-directive