什么是在AngularJS中设置输入框焦点的'Angular方式'?
更具体的要求:
- 当打开Modal时,在此Modal内预定义的
<input>
上设置焦点。 - 每次
<input>
变为可见(例如通过单击某个按钮),都将其设置为焦点。
我尝试使用autofocus
实现第一个要求, 但这只在Modal第一次打开时,且只在某些浏览器中有效(例如在Firefox中无效)。
什么是在AngularJS中设置输入框焦点的'Angular方式'?
更具体的要求:
<input>
上设置焦点。<input>
变为可见(例如通过单击某个按钮),都将其设置为焦点。我尝试使用autofocus
实现第一个要求, 但这只在Modal第一次打开时,且只在某些浏览器中有效(例如在Firefox中无效)。
- 打开模态框时,将焦点设置在预定义的模态框内<input>元素上。
定义一个指令并让它 $watch 一个属性/触发器,以便知道何时将焦点放在该元素上:
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));
});
}
};
}]);
在渲染模态框时,似乎需要使用 $timeout。
'2.' 每次 <input> 可见(例如通过单击某个按钮),都将焦点设置在它上面。
创建一个类似上面的指令。监视一些作用域属性,当它变为 true 时(在 ng-click 处理程序中设置它),执行 element[0].focus()
。根据您的用例,您可能需要或不需要 $timeout:
<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;
//});
}
});
}
};
});
2013年7月更新:我看到一些人使用我的原始隔离作用域指令,然后在嵌入式输入字段(即模态框中的输入字段)中遇到问题。一个没有新作用域(或可能是新的子作用域)的指令应该可以减轻一些痛苦。所以我更新了答案,不再使用隔离作用域。以下是原始答案:
1. 使用隔离作用域的原始答案:
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的原始答案,使用隔离作用域:
<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;
//});
}
});
}
};
});
由于我们需要在指令中重置trigger/focusInput属性,所以使用'='进行双向数据绑定。在第一个指令中,'@'已经足够了。此外,请注意,在使用'@'时,我们将触发值与"true"进行比较,因为'@'始终会生成字符串。
##(EDIT: 下面解释中有更新的解决方案)
Mark Rajcok是个好人,他的答案是有效的,但是它存在缺陷(抱歉Mark)...
...尝试使用布尔变量来聚焦于输入框,然后模糊该输入框,再次尝试使用它来聚焦于输入框。这样不行,除非您将该布尔变量重置为false,然后$digest,然后将其重新设置为true。即使在表达式中使用字符串比较,也必须强制更改字符串为其他内容,$digest然后再将其改回。 (这已经通过模糊事件处理程序得到了解决。)
因此,我提出这个备选方案:
JavaScript非常喜欢事件。事件本质上是松耦合的,而且更好的是,你避免了向你的$digest添加另一个$watch。
app.directive('focusOn', function() {
return function(scope, elem, attr) {
scope.$on(attr.focusOn, function(e) {
elem[0].focus();
});
};
});
现在你可以像这样使用它:
<input type="text" focus-on="newItemAdded" />
然后在应用程序中的任何位置都可以...
$scope.addNewItem = function () {
/* stuff here to add a new item... */
$scope.$broadcast('newItemAdded');
};
这很棒,因为你可以用类似的东西做各种事情。首先,你可以连接已经存在的事件。其次,通过让应用程序的不同部分发布其他部分可以订阅的事件,你可以开始做一些聪明的事情。
无论如何,这种类型的东西对我来说都是“事件驱动”的。我认为作为 Angular 开发人员,我们会尽力将 $scope 形状的楔子敲入事件形状的孔中。
这是最好的解决方案吗?我不知道。它是一个解决方案。
在@ShimonRachlenko的下方评论后,我稍微改变了我的方法。现在我使用一个服务和一个指令的组合,在“幕后”处理事件:
除此之外,它与上面概述的原则相同。
###用法
<input type="text" focus-on="focusMe"/>
app.controller('MyCtrl', function($scope, focus) {
focus('focusMe');
});
###Source
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);
});
}
});
$timeout
包装对 $broadcast
的调用。否则,这是一个不错的解决方案。 - Shimon Rachlenko我发现其他答案有些过于复杂了,其实你只需要这个就行了。
app.directive('autoFocus', function($timeout) {
return {
restrict: 'AC',
link: function(_scope, _element) {
$timeout(function(){
_element[0].focus();
}, 0);
}
};
});
使用方法为
<input name="theInput" auto-focus>
我们使用超时时间来让DOM中的元素渲染,即使为零,它也至少等待那个时间 - 这样就可以在模态框等情况下正常工作。
ng-click
中使用它的人:假设点击按钮时在您的输入上有ng-click="showInput = !showInput"
。然后,在您的实际输入中,添加ng-if="showInput"
。切换按钮将使指令每次重新运行。我曾经遇到过这个问题,因为我使用了错误的方法ng-show
。 - JVGHTML有一个属性叫做autofocus
。
<input type="text" name="fname" autofocus>
您也可以使用内置在angular中的jqlite功能。
angular.element('.selector').trigger('focus');
这很好用,是一种集成Angular的焦点控制输入框的方式。
angular.element('#elementId').focus()
尽管这不是一个纯粹的Angular方式来完成任务,但语法遵循Angular风格。jQuery在Angular中起到了直接和间接地访问DOM的作用(jQLite => JQuery Light)。
如果需要,这段代码可以很容易地放入一个简单的Angular指令中,其中元素是直接可访问的。
Looking up elements via selectors is not supported by jqLite!
- JVGangular.element
就成为了$() / jQuery()
的包装器。因此,如果没有这个,它将无法工作,而且基本上您仍然在使用jQuery(但如果我错了请纠正我)。 - JVG我认为在创建元素时,$timeout 不是一个好的聚焦方式。这里有一种使用内置的 Angular 功能的方法,可以从 Angular 文档中挖掘出来。请注意,“link”属性可以分成“pre”和“post”,用于 pre-link 和 post-link 函数。
工作示例: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 />
完整的AngularJS指令文档:https://docs.angularjs.org/api/ng/service/$compile
这是我的原始解决方案:
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()});
});
}
};
});
并且HTML代码如下:
<button ng-click="isVisible = !isVisible">Toggle input</button>
<input ng-show="isVisible" auto-focus="{{ isVisible }}" value="auto-focus on" />
在使用ng-show时,它可以将输入框聚焦。这里没有使用$watch或$on。
<input focus="someFocusVariable">
如果您在控制器的任何地方将someFocusVariable作为范围变量true
,则输入框会获得焦点。如果您想要“失去焦点”,则可以将someFocusVariable设置为false。这就像Mark Rajcok的第一个答案,但具有双向绑定。
以下是指令:
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);
})
}
}
});
使用方法:
<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>
这是代码演示:
对于那些使用Bootstrap插件的Angular用户:
http://angular-ui.github.io/bootstrap/#/modal
您可以钩入模态实例的opened
承诺:
modalInstance.opened.then(function() {
$timeout(function() {
angular.element('#title_input').trigger('focus');
});
});
modalInstance.result.then(function ( etc...
50ms
的$timeout
而不是0
。 - lk_vc
ng-model
,当我使用具有隔离作用域的这个指令时模型值会丢失。如果我尝试没有隔离作用域的Josh版本,则不会出现此问题。作为一名新手,我希望能理解其中的区别。这是一个Plunker演示了它。 - Sunil D.value != "true"
时解除绑定观察器,并且这似乎解决了我的问题。 - Andrew Brown