如何在 angular 中使用 added/removed 输入管理 focus/cursor 位置?
How to manage focus/cursor position with added/removed inputs in angular?
我正在制定一个指令,该指令应在所有输入字段都已填写时自动创建另一个输入字段。所以在这个例子中,当我停止输入第三个输入时,第四个将出现,但我的光标应该保留在第三个输入中。
问题是,在我的代码中,最终输入始终存在,所以当我更新数据并将其值移至倒数第二个输入时,它会保留焦点,而我无法将第二个焦点放在最后一次输入足够快,以防止用户在去抖动间隔结束后快速开始键入时出现闪烁和字符丢失。
我怎样才能使它正常工作?
这个问题的 XY 版本是:如何在 ng-repeat 向 DOM 添加新元素后同步执行操作?
plunk
scope: {
// String[]
items: '=',
// 'input' or 'textarea'
type: '@',
// 'text', 'email', 'url', etc.
inputType: '@'
},
link: function (scope, element) {
scope._inputType = scope.inputType || 'text';
scope.temp = {
item: ""
};
var $el = jQuery(element.get(0));
// add another input when the user stops typing in the last input
scope.$watch('temp.item', (debounce)(function(){
if (scope.temp.item) {
scope.items.push(scope.temp.item);
scope.temp.item = "";
var elLast = $el.find('.item-last');
elLast.blur();
var currentInput = elLast.prev('.item-input').find('input').get(0);
var tries = 100;
// use nextTick to reduce time-to-update
// this often fires a few times before angular actually updates anything
nextTick(function again(){
var target = elLast.prev('.item-input').find('input');
// has angular updated? if not: retry
if (target.get(0) === currentInput && (--tries) > 0) {
return nextTick(again);
}
// focus and move cursor to end
target.focus();
var len = target.val().length;
target.get(0).setSelectionRange(len, len);
}, 0, false);
}
}, 500));
// remove empty items after a few seconds
scope.$watch('items', debounce(function(){
var items = scope.items;
var inputs = $el.find('.item-input > *');
var emptyBeforeFocused = 0;
var focusedIndex = null;
for (var i=0; i<inputs.length-1; i++) {
if (inputs.eq(i).is(':focus')) {
focusedIndex = i;
break;
}
if(!items[i]) {
emptyBeforeFocused++;
}
}
var originalLength = scope.items.length;
scope.items = scope.items.filter(x => x.length);
// we need to fix focus if a previous element was removed
if (scope.items.length !== originalLength && emptyBeforeFocused > 0 && focusedIndex) {
scope.$evalAsync(function(){
// grab the index of the focused input, minus the preceding removed inputs
// i.e. the input's new index
var target = $el.find('.item-input > *').eq(focusedIndex - emptyBeforeFocused);
// focus and move cursor to end
target.focus();
var val = scope.type === 'textarea' ? target.text() : target.val();
var len = val.length;
target.get(0).setSelectionRange(len, len);
}, 0, false);
}
}, 5000));
}
<div>
<div ng-if="type === 'input'">
<div class="item-input item-type-input" ng-repeat="item in items track by $index">
<input type="text" ng-model="items[$index]" class="form-control" />
</div>
<div class="item-input item-type-input item-last">
<input type="text" ng-model="temp.item" class="form-control" />
</div>
</div>
<div ng-if="type === 'textarea'">
<div class="item-input item-type-textarea" ng-repeat="item in items track by $index">
<textarea ng-model="items[$index]" class="form-control" ></textarea>
</div>
<div class="item-input item-type-textarea item-last">
<textarea ng-model="temp.item" class="form-control" ></textarea>
</div>
</div>
</div>
.directive('resetFocus', [function(){
return function(scope, element, attr, ctrl) {
if(scope.$last){
element.find('input').focus();
}
};
}])
<div class="item-input item-type-input" ng-repeat="item in items track by $index" reset-focus>
<input type="text" ng-model="items[$index]" class="form-control" />
</div>
由于您也在进行删除,因此您可能需要 $watch('$last')
.directive('resetFocus', [function(){
return function(scope, element, attr, ctrl) {
if(scope.$last){
element.find('input').focus();
}
scope.$watch('$last', function(){
if(scope.$last){
element.find('input').focus();
}
});
};
}])
该指令在创建时将焦点设置在 ng-repeat 中的最后一个输入元素上。它利用了 ng-repeat 会将 $last 添加到作用域这一事实。
我正在制定一个指令,该指令应在所有输入字段都已填写时自动创建另一个输入字段。所以在这个例子中,当我停止输入第三个输入时,第四个将出现,但我的光标应该保留在第三个输入中。
问题是,在我的代码中,最终输入始终存在,所以当我更新数据并将其值移至倒数第二个输入时,它会保留焦点,而我无法将第二个焦点放在最后一次输入足够快,以防止用户在去抖动间隔结束后快速开始键入时出现闪烁和字符丢失。
我怎样才能使它正常工作?
这个问题的 XY 版本是:如何在 ng-repeat 向 DOM 添加新元素后同步执行操作?
plunk
scope: {
// String[]
items: '=',
// 'input' or 'textarea'
type: '@',
// 'text', 'email', 'url', etc.
inputType: '@'
},
link: function (scope, element) {
scope._inputType = scope.inputType || 'text';
scope.temp = {
item: ""
};
var $el = jQuery(element.get(0));
// add another input when the user stops typing in the last input
scope.$watch('temp.item', (debounce)(function(){
if (scope.temp.item) {
scope.items.push(scope.temp.item);
scope.temp.item = "";
var elLast = $el.find('.item-last');
elLast.blur();
var currentInput = elLast.prev('.item-input').find('input').get(0);
var tries = 100;
// use nextTick to reduce time-to-update
// this often fires a few times before angular actually updates anything
nextTick(function again(){
var target = elLast.prev('.item-input').find('input');
// has angular updated? if not: retry
if (target.get(0) === currentInput && (--tries) > 0) {
return nextTick(again);
}
// focus and move cursor to end
target.focus();
var len = target.val().length;
target.get(0).setSelectionRange(len, len);
}, 0, false);
}
}, 500));
// remove empty items after a few seconds
scope.$watch('items', debounce(function(){
var items = scope.items;
var inputs = $el.find('.item-input > *');
var emptyBeforeFocused = 0;
var focusedIndex = null;
for (var i=0; i<inputs.length-1; i++) {
if (inputs.eq(i).is(':focus')) {
focusedIndex = i;
break;
}
if(!items[i]) {
emptyBeforeFocused++;
}
}
var originalLength = scope.items.length;
scope.items = scope.items.filter(x => x.length);
// we need to fix focus if a previous element was removed
if (scope.items.length !== originalLength && emptyBeforeFocused > 0 && focusedIndex) {
scope.$evalAsync(function(){
// grab the index of the focused input, minus the preceding removed inputs
// i.e. the input's new index
var target = $el.find('.item-input > *').eq(focusedIndex - emptyBeforeFocused);
// focus and move cursor to end
target.focus();
var val = scope.type === 'textarea' ? target.text() : target.val();
var len = val.length;
target.get(0).setSelectionRange(len, len);
}, 0, false);
}
}, 5000));
}
<div>
<div ng-if="type === 'input'">
<div class="item-input item-type-input" ng-repeat="item in items track by $index">
<input type="text" ng-model="items[$index]" class="form-control" />
</div>
<div class="item-input item-type-input item-last">
<input type="text" ng-model="temp.item" class="form-control" />
</div>
</div>
<div ng-if="type === 'textarea'">
<div class="item-input item-type-textarea" ng-repeat="item in items track by $index">
<textarea ng-model="items[$index]" class="form-control" ></textarea>
</div>
<div class="item-input item-type-textarea item-last">
<textarea ng-model="temp.item" class="form-control" ></textarea>
</div>
</div>
</div>
.directive('resetFocus', [function(){
return function(scope, element, attr, ctrl) {
if(scope.$last){
element.find('input').focus();
}
};
}])
<div class="item-input item-type-input" ng-repeat="item in items track by $index" reset-focus>
<input type="text" ng-model="items[$index]" class="form-control" />
</div>
由于您也在进行删除,因此您可能需要 $watch('$last')
.directive('resetFocus', [function(){
return function(scope, element, attr, ctrl) {
if(scope.$last){
element.find('input').focus();
}
scope.$watch('$last', function(){
if(scope.$last){
element.find('input').focus();
}
});
};
}])
该指令在创建时将焦点设置在 ng-repeat 中的最后一个输入元素上。它利用了 ng-repeat 会将 $last 添加到作用域这一事实。