【问题标题】:Easy dom manipulation in AngularJS - click a button, then set focus to an input elementAngularJS 中的简单 dom 操作 - 单击按钮,然后将焦点设置到输入元素
【发布时间】:2013-03-09 06:22:25
【问题描述】:

我有这个角码:

<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 方法吗?在jQuery中使用.closest().find()等函数?

更新:

我发现这个 hack 有效(但它似乎仍然不是正确的解决方案)

function doSomething(element,$event) {
   setTimeout(function(){
     $($event.srcElement).closest('.element-wrapper').find('input').focus();
   },0)
}

我用 setTimeout 包装它,所以在 Angular 完成所有操作后,它会专注于输入元素。

【问题讨论】:

  • 您应该查看第二个包装器 - 而不是第一个
  • @Pete,我写错了类名。我修好了它。这不是查找元素的问题,而是如何使用 AngularJS 正确完成它的问题
  • 你能提供一个你想要达到的目标吗?
  • 第二个 hack 真的很丑,或者,你应该使用 $timeout 而不是 setTimeout (虽然我不建议这样做)

标签: javascript jquery angularjs


【解决方案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() 方法。

find('input')[0] 让我们可以访问 DOM 元素,允许我们使用 JavaScript focus() 方法(而不是需要 jQuery 的 find('input').focus())。

【讨论】:

  • 感谢指导样本。我有个小问题:如果指令只用一次,做指令是不是有点重?
  • @Freewind,任何类型的 DOM 活动(遍历、焦点()、添加/删除元素或类)都应该在指令中完成。当然,您可以将一行 jQuery 放入控制器中来执行此操作,但是您违反了“干净的关注点分离”。这完全取决于你希望你的架构有多干净。关于“使用一次”,您可以通过传入 id 或类名或其他东西作为属性来使该指令更加通用/可重用,然后指令可以找到它。
  • 虽然“角度方式”做所有事情并在这里使用 $timeout 很诱人,但我认为你不应该这样做有两个原因:1)它会触发一个完全没有必要的 $digest,如果您不更改模型,更重要的是 2)在另一个指令(可能应用于输入)中处理焦点事件时,您不能调用 scope.$apply(),因为它会告诉您应用已经在进行中。
  • @user1620696,是的,是的。指令属性是将信息传递给指令的好方法。
  • 现在,我喜欢 Angular 并将其用于一些相当复杂的应用程序。这种类型的单指令用于非常简单的 dom 操作只是英国媒体报道。如果你喜欢的话,那整件事就是一行更具可读性的 vanilla js 或 jQuery。
【解决方案2】:

我最近一直在研究 AngularJS,遇到了类似的情况。

我正在努力从主 Angular 页面更新 Todo 示例应用程序,以便在您双击待办事项项时添加“编辑”模式。

我能够使用基于模型/状态的方法解决我的问题。如果您的应用程序以类似的方式工作(当模型上的某些条件为真时,您希望将焦点设置在某个字段上),那么这也可能对您有用。

我的方法是在用户双击待办事项标签时将model.editing 属性设置为true - 这会显示可编辑的输入并隐藏常规的不可编辑标签和复选框。 我们还有一个名为 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
                        });
                    });

                });

            };
        }
    ]);

【讨论】:

  • 这对我有用,但我必须更改“element.select();”到“元素[0].select();”
【解决方案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's 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">
    ...
    

    当您不使用输入时,请务必重置此变量。您可以添加一个足够简单的 ng-blur 指令以将其更改回来,或者添加另一个将其重置为 false 的 ng-click 事件。将其设置为 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});
            });
        });
        }
    }]);
    

    【讨论】:

    • 值得注意的是,这里假设已经加载了 jQuery。没有jQuery的element.focus();需要改成element[0].focus();
    • 我必须为 focusBool 指令指定 $timeout 依赖项,才能使其正常工作。该解决方案最低限度地满足了我的需求。谢谢。
    【解决方案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();
                    });
                });
            }
        };
    });
    

    假设:

    • 您的给予者和接受者就在附近。
    • 当在一个页面上多次使用此 ID 时,所使用的 ID 是唯一的,或者给予和接受者位于 DOM 的一个独立分支中。

    【讨论】:

      【解决方案6】:

      对于使用 .closest() 方法,我建议您应用原型继承机制狐狸扩展角度机会。就像这样:

      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 &lt;input id="input{{$index}}" .. /&gt; 并将 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();
            });
          }
        });
        

        【讨论】:

          猜你喜欢
          • 2017-04-24
          • 1970-01-01
          • 1970-01-01
          • 2017-08-23
          • 2016-02-21
          • 2019-03-13
          • 1970-01-01
          • 2019-04-09
          • 1970-01-01
          相关资源
          最近更新 更多