Простое манипулирование dom в AngularJS - нажмите кнопку, затем установите фокус на входной элемент
У меня есть этот angular код:
<div class="element-wrapper" ng-repeat="element in elements">
<div class="first-wrapper">
<div class="button" ng-click="doSomething(element,$event)">{{element.name}}</div>
</div>
<div class="second-wrapper">
<input type="text" value="{{element.value}}">
</div>
</div>
Что я хочу:, когда пользователь нажимает кнопку - элемент ввода будет сфокусирован.
Как найти элемент ввода после щелчка элемента кнопки и сфокусировать его?
Я могу сделать функцию, которая выглядит так:
function doSomething(element,$event) {
//option A - start manipulating in the dark:
$event.srcElement.parentNode.childNodes[1]
//option B - wrapping it with jQuery:
$($event.srcElement).closest('.element-wrapper').find('input').focus();
}
Ни один из них не работает - есть ли более приятный способ angular сделать это? Использование таких функций, как .closest()
и .find()
, как в jQuery?
Update:
Я нашел этот хак для работы (но он все еще не кажется правильным решением):
function doSomething(element,$event) {
setTimeout(function(){
$($event.srcElement).closest('.element-wrapper').find('input').focus();
},0)
}
Я обматываю его с помощью setTimeout, после того как angular завершает все свои манипуляции, он фокусируется на элементе ввода.
Ответы
Ответ 1
DOM-манипуляция должна быть в директиве вместо контроллера. Я бы определил директиву focusInput
и использовал ее на кнопке:
<div class="button" focus-input>{{element.name}}</div>
Директива
app.directive('focusInput', function($timeout) {
return {
link: function(scope, element, attrs) {
element.bind('click', function() {
$timeout(function() {
element.parent().parent().find('input')[0].focus();
});
});
}
};
});
Plunker
Так как jqLite довольно ограничен с точки зрения методов обхода DOM, мне пришлось использовать parent().parent()
. Вы можете использовать jQuery или некоторые методы JavaScript.
Как вы уже выяснили, требуется $timeout
, чтобы метод focus()
вызывался после рендеринга браузера (т.е. завершает обработку события click).
find('input')[0]
дает нам доступ к элементу DOM, позволяя нам использовать метод JavaScript focus()
(а не find('input').focus()
, который потребует jQuery).
Ответ 2
Недавно я взглянул на AngularJS и столкнулся с подобной ситуацией.
Я работал над обновлением приложения примера Todo с главной страницы angular, чтобы добавить режим редактирования при двойном щелчке по тодовому элементу.
Мне удалось решить мою проблему, используя модельный/государственный подход. Если ваше приложение работает аналогичным образом (вы хотите установить фокус на поле, когда какое-либо условие для модели истинно), то это может сработать и для вас.
Мой подход заключается в том, чтобы установить свойство model.editing
на true
, когда пользователь дважды щелкает по метке todo - это показывает редактируемый ввод и скрывает обычную не редактируемую метку и флажок.
У нас также есть настраиваемая директива под названием focusInput
, которая имеет часы с тем же свойством model.editing
и будет устанавливать фокус на текстовое поле при изменении значения:
<li ng-repeat="todo in todos">
<div>
<!-- Regular display view. -->
<div ng-show="todo.editing == false">
<label class="done-{{todo.done}}" ng-dblclick="model.editing = true">
<input type="checkbox" ng-model="todo.done"/>{{todo.text}}
</label>
</div>
<!-- Editable view. -->
<div ng-show="todo.editing == true">
<!--
- Add the `focus-input` directive with the statement "todo.editing == true".
This is the element that will receive focus when the statement evaluates to true.
- We also add the `todoBlur` directive so we can cancel editing when the text field loses focus.
-->
<input type="text" ng-model="todo.text" focus-input="todo.editing == true" todo-blur="todo.editing = false"/>
</div>
</div>
</li>
Вот директива focusInput
, которая будет фокусировать внимание на текущем элементе, когда какое-либо условие оценивается как true
:
angular.module('TodoModule', [])
// Define a new directive called `focusInput`.
.directive('focusInput', function($timeout){
return function(scope, element, attr){
// Add a watch on the `focus-input` attribute.
// Whenever the `focus-input` statement changes this callback function will be executed.
scope.$watch(attr.focusInput, function(value){
// If the `focus-input` statement evaluates to `true`
// then use jQuery to set focus on the element.
if (value){
$timeout(function(){
element.select();
});
}
});
};
})
// Here is the directive to raise the 'blur' event.
.directive('todoBlur', [
'$parse', function($parse){
return function(scope, element, attr){
var fn = $parse(attr['todoBlur']);
return element.on('blur', function(event){
return scope.$apply(function(){
return fn(scope, {
$event: event
});
});
});
};
}
]);
Ответ 3
Вот директива, которая инициирует событие фокусировки на целевом элементе dom:
Директива AngularJs:
app.directive('triggerFocusOn', function($timeout) {
return {
link: function(scope, element, attrs) {
element.bind('click', function() {
$timeout(function() {
var otherElement = document.querySelector('#' + attrs.triggerFocusOn);
if (otherElement) {
otherElement.focus();
}
else {
console.log("Can't find element: " + attrs.triggerFocusOn);
}
});
});
}
};
});
html:
<button trigger-focus-on="targetInput">Click here to focus on the other element</button>
<input type="text" id="targetInput">
Живой пример в Plunker
Ответ 4
Мне нужно было создать учетную запись, чтобы обеспечить легкий ответ.
//Add a bool to your controller scope that indicates if your element is focused
... //ellipsis used so I don't write the part you should know
$scope.userInputActivate = false;
...
//Add a new directive to your app stack
...
.directive('focusBool', function() {
return function(scope, element, attrs) {
scope.$watch(attrs.focusBool, function(value) {
if (value) $timeout(function() {element.focus();});
});
}
})
...
<!--Now that our code is watching for a scope boolean variable, stick that variable on your input element using your new directive, and manipulate that variable as desired.-->
...
<div class="button" ng-click="userInputActivate=true">...</div>
...
<input type="text" focus-Bool="userInputActivate">
...
Обязательно reset эту переменную, если вы не используете вход. Вы можете добавить директиву ng-blur достаточно легко, чтобы изменить его, или другое событие ng-click, которое сбрасывает его на false. Установив его в false, он будет готов в следующий раз. Вот пример директивы ng-blur, который я нашел, если у вас есть проблемы с поиском.
.directive('ngBlur', ['$parse', function($parse) {
return function(scope, element, attr) {
var fn = $parse(attr['ngBlur']);
element.bind('blur', function(event) {
scope.$apply(function() {
fn(scope, {$event:event});
});
});
}
}]);
Ответ 5
Вот что я придумал. Я начал с решения Mark Rajcok выше, а затем переехал, чтобы упростить повторное использование. Он настраивается и не требует никакого кода в вашем контроллере. Фокус - это чистый аспект представления и не требует кода контроллера
HTML:
<div id="focusGroup">
<div>
<input type="button" value="submit" pass-focus-to="focusGrabber" focus-parent="focusGroup">
</div>
<div>
<input type="text" id="focusGrabber">
</div>
</div>
директива:
chariotApp.directive('passFocusTo', function ($timeout) {
return {
link: function (scope, element, attrs) {
element.bind('click', function () {
$timeout(function () {
var elem = element.parent();
while(elem[0].id != attrs.focusParent) {
elem = elem.parent();
}
elem.find("#"+attrs.passFocusTo)[0].focus();
});
});
}
};
});
предположение:
- Ваш даритель и помощник рядом.
- при использовании этого многократного использования на одном идентификаторе страницы используются уникальные, или данные и получатели находятся в изолированной ветки DOM.
Ответ 6
Для использования метода .closest() я предлагаю вам применить механизм наследования прототипа fox expand angular. Точно так же:
angular.element.prototype.closest = (parentClass)->
$this = this
closestElement = undefined
while $this.parent()
if $this.parent().hasClass parentClass
closestElement = $this.parent()
break
$this = $this.parent()
closestElement
Разметка:
<span ng-click="removeNote($event)" class="remove-note"></span>
Использование:
$scope.removeNote = ($event)->
currentNote = angular.element($event.currentTarget).closest("content-list_item")
currentNote.remove()
Ответ 7
Чтобы найти вход, добавьте его Id <input id="input{{$index}}" .. />
и передайте индекс ngRepeat как параметр в функцию ng-click="doSomething(element,$event, $index)"
<div class="element-wrapper" ng-repeat="element in elements">
<div class="first-wrapper">
<div class="button" ng-click="doSomething(element,$event, $index)">{{element.name}}</div>
</div>
<div class="second-wrapper">
<input id="input{{$index}}" type="text" value="{{element.value}}">
</div>
</div>
В функции используйте $timeout
с нулевой задержкой, чтобы подождать до конца рендеринга DOM. Затем вход можно найти в getElementById
в $timeout
. Не забудьте добавить $timeout
в контроллер.
.controller("MyController", function ($scope, $timeout)
{
$scope.doSomething = function(element,$event, index) {
//option A - start manipulating in the dark:
$event.srcElement.parentNode.childNodes[1]
$timeout(function ()
{
document.getElementById("input" + index).focus();
});
}
});